/**
 * Gooflow在线流程图设计器
 * Version: 1.3.2
 * Copyright: foolegg126(sdlddr)
 */

//预先定义几个公用方法
//获取一个DIV的绝对坐标的功能函数,即使是非绝对定位,一样能获取到
function _elCsys(dom) {
    var t = dom.offsetTop;
    var l = dom.offsetLeft;
    dom = dom.offsetParent;
    while (dom) {
        t += dom.offsetTop;
        l += dom.offsetLeft;
        dom = dom.offsetParent;
    }
    return { top: t, left: l };
}
//兼容各种浏览器的,获取鼠标真实位置
function _mouseP(ev) {
    if (!ev) ev = window.event;
    if (ev.pageX || ev.pageY) {
        return { x: ev.pageX, y: ev.pageY };
    }
    return {
        x: ev.clientX + document.documentElement.scrollLeft - document.body.clientLeft,
        y: ev.clientY + document.documentElement.scrollTop - document.body.clientTop
    };
}
//计算两个结点间要连折线的话，连线的所有坐标
function calcPolyPoints(n1, n2, type, M, scale) {
    if (!scale) scale = 1.0;
    var N1 = { left: n1.left * scale, top: n1.top * scale, width: n1.width * scale, height: n1.height * scale };
    var N2 = { left: n2.left * scale, top: n2.top * scale, width: n2.width * scale, height: n2.height * scale };
    M = M * scale;
    //开始/结束两个结点的中心
    var SP = { x: N1.left + N1.width / 2, y: N1.top + N1.height / 2 };
    var EP = { x: N2.left + N2.width / 2, y: N2.top + N2.height / 2 };
    var m1 = [], m2 = [], sp, ep;
    //如果是允许中段可左右移动的折线,则参数M为可移动中段线的X坐标
    //粗略计算起始点
    sp = [SP.x, SP.y];
    ep = [EP.x, EP.y];
    if (type === "lr") {
        //粗略计算2个中点
        m1 = [M, SP.y];
        m2 = [M, EP.y];
        //再具体分析修改开始点和中点1
        if (m1[0] > N1.left && m1[0] < N1.left + N1.width) {
            m1[1] = (SP.y > EP.y ? N1.top : N1.top + N1.height);
            sp[0] = m1[0]; sp[1] = m1[1];
        }
        else {
            sp[0] = (m1[0] < N1.left ? N1.left : N1.left + N1.width)
        }
        //再具体分析中点2和结束点
        if (m2[0] > N2.left && m2[0] < N2.left + N2.width) {
            m2[1] = (SP.y > EP.y ? N2.top + N2.height : N2.top);
            ep[0] = m2[0]; ep[1] = m2[1];
        }
        else {
            ep[0] = (m2[0] < N2.left ? N2.left : N2.left + N2.width)
        }
    }
    //如果是允许中段可上下移动的折线,则参数M为可移动中段线的Y坐标
    else if (type === "tb") {
        //粗略计算2个中点
        m1 = [SP.x, M];
        m2 = [EP.x, M];
        //再具体分析修改开始点和中点1
        if (m1[1] > N1.top && m1[1] < N1.top + N1.height) {
            m1[0] = (SP.x > EP.x ? N1.left : N1.left + N1.width);
            sp[0] = m1[0]; sp[1] = m1[1];
        }
        else {
            sp[1] = (m1[1] < N1.top ? N1.top : N1.top + N1.height)
        }
        //再具体分析中点2和结束点
        if (m2[1] > N2.top && m2[1] < N2.top + N2.height) {
            m2[0] = (SP.x > EP.x ? N2.left + N2.width : N2.left);
            ep[0] = m2[0]; ep[1] = m2[1];
        }
        else {
            ep[1] = (m2[1] < N2.top ? N2.top : N2.top + N2.height);
        }
    }
    return { start: sp, m1: m1, m2: m2, end: ep };
}
//计算两个结点间要连直线的话，连线的开始坐标和结束坐标
function calcStartEnd(n1, n2, scale) {
    if (!scale) scale = 1.0;
    var X_1, Y_1, X_2, Y_2;
    //X判断：
    var x11 = n1.left * scale, x12 = n1.left * scale + n1.width * scale, x21 = n2.left * scale, x22 = n2.left * scale + n2.width * scale;
    //结点2在结点1左边
    if (x11 >= x22) {
        X_1 = x11; X_2 = x22;
    }
    //结点2在结点1右边
    else if (x12 <= x21) {
        X_1 = x12; X_2 = x21;
    }
    //结点2在结点1水平部分重合
    else if (x11 <= x21 && x12 >= x21 && x12 <= x22) {
        X_1 = (x12 + x21) / 2; X_2 = X_1;
    }
    else if (x11 >= x21 && x12 <= x22) {
        X_1 = (x11 + x12) / 2; X_2 = X_1;
    }
    else if (x21 >= x11 && x22 <= x12) {
        X_1 = (x21 + x22) / 2; X_2 = X_1;
    }
    else if (x11 <= x22 && x12 >= x22) {
        X_1 = (x11 + x22) / 2; X_2 = X_1;
    }

    //Y判断：
    var y11 = n1.top * scale, y12 = n1.top * scale + n1.height * scale, y21 = n2.top * scale, y22 = n2.top * scale + n2.height * scale;
    //结点2在结点1上边
    if (y11 >= y22) {
        Y_1 = y11; Y_2 = y22;
    }
    //结点2在结点1下边
    else if (y12 <= y21) {
        Y_1 = y12; Y_2 = y21;
    }
    //结点2在结点1垂直部分重合
    else if (y11 <= y21 && y12 >= y21 && y12 <= y22) {
        Y_1 = (y12 + y21) / 2; Y_2 = Y_1;
    }
    else if (y11 >= y21 && y12 <= y22) {
        Y_1 = (y11 + y12) / 2; Y_2 = Y_1;
    }
    else if (y21 >= y11 && y22 <= y12) {
        Y_1 = (y21 + y22) / 2; Y_2 = Y_1;
    }
    else if (y11 <= y22 && y12 >= y22) {
        Y_1 = (y11 + y22) / 2; Y_2 = Y_1;
    }
    return { "start": [X_1, Y_1], "end": [X_2, Y_2] };
}
//初始化折线中段的X/Y坐标,mType='rb'时为X坐标,mType='tb'时为Y坐标
function getMValue(n1, n2, mType, scale) {
    if (!scale) scale = 1.0;
    if (mType === "lr") {
        return (n1.left * scale + n1.width * scale / 2 + n2.left * scale + n2.width * scale / 2) / 2;
    }
    else if (mType === "tb") {
        return (n1.top * scale + n1.height * scale / 2 + n2.top * scale + n2.height * scale / 2) / 2;
    }
}
//构造类：
var GooFlow = function (selector, property) {
    console.log('Your browser\'s navigator.userAgent is:', navigator.userAgent);
    if (navigator.userAgent.indexOf("MSIE 8.0") > 0 || navigator.userAgent.indexOf("MSIE 7.0") > 0 || navigator.userAgent.indexOf("MSIE 6.0") > 0)
        GooFlow.prototype.useSVG = "";
    else GooFlow.prototype.useSVG = "1";
    //初始化区域图的对象
    this.$bgDiv = $(selector);//最父框架的DIV
    this.$bgDiv.addClass("GooFlow");
    this.$id = this.$bgDiv.attr("id") || 'GooFlow_' + new Date().getTime();
    if (property.colors && typeof property.colors === 'object') {
        $.extend(GooFlow.color, property.colors);
    }
    this.$bgDiv.css("color", GooFlow.color.font);
    if (GooFlow.color.main) {
        this.$bgDiv.append('<style>.GooFlow_tool_btndown{background-color:' + GooFlow.color.main + '}</style>');
    }
    var width = (property.width || this.$bgDiv.width());
    var height = (property.height || this.$bgDiv.height());
    this.$bgDiv.css({ width: width + "px", height: height + "px" });
    this.$tool = null;//左侧工具栏对象
    this.$head = null;//顶部标签及工具栏按钮
    this.$title = "newFlow_1";//流程图的名称
    this.$nowType = "cursor";//当前要绘制的对象类型
    this.$lineData = {};
    this.$lineCount = 0;
    this.$nodeData = {};
    this.$nodeCount = 0;
    this.$areaData = {};
    this.$areaCount = 0;
    this.$lineDom = {};
    this.$nodeDom = {};
    this.$areaDom = {};
    this.$max = property.initNum || 1;//计算默认ID值的起始SEQUENCE
    this.$focus = "";//当前被选定的结点/转换线ID,如果没选中或者工作区被清空,则为""
    //this.$cursor="default";//鼠标指针在工作区内的样式
    this.$editable = false;//工作区是否可编辑
    this.$deletedItem = {};//在流程图的编辑操作中被删除掉的元素ID集合,元素ID为KEY,元素类型(node,line.area)为VALUE
    this.$workExtendStep = 200;//在自动/手动扩展可编辑区时，一次扩展后宽/高增加多少像素
    this.$scale = 1.00;//工作区内容的缩放比例，从0.1至无穷大，初始默认为1
    var headHeight = 0;
    var tmp = "", titleText;
    if (property.haveHead) {
        tmp = "<div class='GooFlow_head' " + (GooFlow.color.main ? "style='border-bottom-color:" + GooFlow.color.main + "'" : "") + ">";
        if (property.headLabel) {
            tmp += "<label title='" + (property.initLabelText || "newFlow_1") + "' "
                + (GooFlow.color.main ? "style='background:" + GooFlow.color.main + "'" : "") + ">"
                + (property.initLabelText || "newFlow_1") + "</label>";
        }
        if (property.headBtns)
            for (var x = 0; x < property.headBtns.length; ++x) {
                if (!property.useOperStack && (property.headBtns[x] === 'undo' || property.headBtns[x] === 'redo')) continue;
                titleText = GooFlow.remarks.headBtns[property.headBtns[x]] ? " title='" + GooFlow.remarks.headBtns[property.headBtns[x]] + "'" : "";
                tmp += "<a href='javascript:void(0)' class='GooFlow_head_btn'" + titleText + "><i class='ico_" + property.headBtns[x] + "'></i></a>"
            }
        tmp += "</div>";
        this.$head = $(tmp);
        this.$bgDiv.append(this.$head);
        if (property.headBtns) {
            this.$head.find(".ico_undo").parent().addClass("a_disabled");
            this.$head.find(".ico_redo").parent().addClass("a_disabled");
            //以下是当工具栏按钮被点击时触发的事件自定义(虚函数),格式为function(),因为可直接用THIS操作对象本身,不用传参；用户可自行重定义:
            this.onBtnNewClick = null;//新建流程图按钮被点中
            this.onBtnOpenClick = null;//打开流程图按钮定义
            this.onBtnSaveClick = null;//保存流程图按钮定义
            this.onFreshClick = null;//重载流程图按钮定义
            this.onPrintClick = null;//打印流程图按钮定义
            this.$headBtnEvents = property.headBtnEvents;//用户对头部栏另行自定义类型按钮的事件绑定json集合,key为按钮类型名，value为方法定义
            this.$head.on("click", { inthis: this }, function (e) {
                if (!e) e = window.event;
                var tar = e.target;
                if (tar.tagName === "DIV" || tar.tagName === "SPAN") return;
                else if (tar.tagName === "A") tar = tar.childNodes[0];
                var This = e.data.inthis, Class = $(tar).attr("class");
                //定义顶部操作栏按钮的事件
                switch (Class) {
                    case "ico_new": if (This.onBtnNewClick !== null) This.onBtnNewClick(); break;
                    case "ico_open": if (This.onBtnOpenClick !== null) This.onBtnOpenClick(); break;
                    case "ico_save": if (This.onBtnSaveClick !== null) This.onBtnSaveClick(); break;
                    case "ico_undo": This.undo(); break;
                    case "ico_redo": This.redo(); break;
                    case "ico_reload": if (This.onFreshClick !== null) This.onFreshClick(); break;
                    case "ico_print": if (This.onPrintClick !== null) This.onPrintClick(); break;
                    default:
                        if (typeof This.$headBtnEvents !== 'undefined' && typeof This.$headBtnEvents[Class] === 'function') {
                            This.$headBtnEvents[Class]();
                        }
                }
            });
        }
        headHeight = 28;
    }
    var toolWidth = 0;
    if (property.haveTool) {
        this.$bgDiv.append("<div class='GooFlow_tool'" + (property.haveHead ? "" : " style='margin-top:3px'") + "><div style='height:" + (height - headHeight - (property.haveHead ? 5 : 8)) + "px' class='GooFlow_tool_div'></div></div>");
        this.$tool = this.$bgDiv.find(".GooFlow_tool div");
        //未加代码：加入绘图工具按钮
        var titleCursor = GooFlow.remarks.toolBtns["cursor"] ? " title='" + GooFlow.remarks.toolBtns["cursor"] + "'" : "";
        var titleDirect = GooFlow.remarks.toolBtns["direct"] ? " title='" + GooFlow.remarks.toolBtns["direct"] + "'" : "";
        var titleDashed = GooFlow.remarks.toolBtns["dashed"] ? " title='" + GooFlow.remarks.toolBtns["dashed"] + "'" : "";
        this.$tool.append("<div style='margin-bottom:4px'><span/><span/><span/></div>"
            + "<a href='javascript:void(0)'" + titleCursor + " type='cursor' class='GooFlow_tool_btndown' id='" + this.$id + "_btn_cursor'><i class='ico_cursor'/></a>"
            + "<a href='javascript:void(0)'" + titleDirect + " type='direct' class='GooFlow_tool_btn' id='" + this.$id + "_btn_direct'><i class='ico_direct'/></a>"
            + (property.haveDashed ? "<a href='javascript:void(0)'" + titleDashed + " type='dashed' class='GooFlow_tool_btn' id='" + this.$id + "_btn_dashed'><i class='ico_dashed'/></a>" : "")
        );
        if (property.toolBtns && property.toolBtns.length > 0) {
            tmp = "<span/>";
            for (var i = 0; i < property.toolBtns.length; ++i) {
                var tmpType = property.toolBtns[i].split(" ")[0];
                titleText = GooFlow.remarks.toolBtns[tmpType] ? " title='" + GooFlow.remarks.toolBtns[tmpType] + "'" : '';
                tmp += "<a href='javascript:void(0)'" + titleText + " type='" + property.toolBtns[i] + "' id='" + this.$id + "_btn_" + tmpType + "' class='GooFlow_tool_btn'><i class='ico_" + property.toolBtns[i] + "'/></a>";//加入自定义按钮
            }
            this.$tool.append(tmp);
        }
        //加入区域划分框工具开关按钮
        if (property.haveGroup) {
            var titleGroup = GooFlow.remarks.toolBtns["group"] ? " title='" + GooFlow.remarks.toolBtns["group"] + "'" : "";
            this.$tool.append("<span/><a href='javascript:void(0)'" + titleGroup + " type='group' class='GooFlow_tool_btn' id='" + this.$id + "_btn_group'><i class='ico_group'/></a>");
        }
        toolWidth = 31;
        this.$nowType = "cursor";
        //绑定各个按钮的点击事件
        this.$tool.on("click", { inthis: this }, function (e) {
            if (!e) e = window.event;
            var tar;
            switch (e.target.tagName) {
                case "SPAN": return false;
                case "DIV": return false;
                case "I": tar = e.target.parentNode; break;
                case "A": tar = e.target;
            }
            var type = $(tar).attr("type");
            e.data.inthis.switchToolBtn(type);
            return false;
        });
        this.$editable = true;//只有具有工具栏时可编辑
    }

    //确定工作区在设计器中的位置、宽高
    width = width - toolWidth - 9;
    height = height - headHeight - (property.haveHead ? 5 : 8);
    this.$bgDiv.append("<div class='GooFlow_work' style='" + (property.haveHead ? "top:28px;" : "") + (property.haveTool ? "left:34px" : "") + "'></div>");
    this.$workArea = $("<div class='GooFlow_work_inner' style='width:" + width + "px;height:" + height + "px'></div>")
        .attr({ "unselectable": "on", "onselectstart": 'return false', "onselect": 'document.selection.empty()' });
    this.$bgDiv.children(".GooFlow_work").append(this.$workArea);
    //计算工作区相对GooFlow父框架的绝对定位运算值，并保存
    this.t = { top: property.haveHead ? 28 : 3, left: property.haveTool ? 34 : 3 };

    //绑定工作区事件
    this.$workArea.on("click", { inthis: this }, function (e) {
        if (!e) e = window.event;
        var This = e.data.inthis;
        var type = This.$nowType;
        var oname = "node_" + This.$max;
        if (type === "cursor") {
            var tar = $(e.target);
            var n = tar.prop("tagName");
            if (n === "svg" || (n === "DIV" && tar.prop("class").indexOf("GooFlow_work") > -1) || n === "LABEL") {
                console.log(n);
                if (This.$lineOper && This.$lineOper.data("tid")) {
                    This.focusItem(This.$lineOper.data("tid"), false);
                }
                else { This.blurItem(); }
            }
            return;
        }
        else if (type === "direct" || type === "dashed" || type === "group") {
            return;
        }

        //todo: add luomingui 2020-11-22
        if (type.indexOf("start ") > -1) {
            oname = "开始";
        } else if (type.indexOf("end ") > -1) {
            oname = "结束";
        } else if (type.indexOf("node") > -1) {
            oname = "普通节点" + This.$max;
        } else if (type.indexOf("join") > -1) {
            oname = "联合节点" + This.$max;
        } else if (type.indexOf("fork") > -1) {
            oname = "分支节点" + This.$max;
        }

        if (!This.$editable) return;
        var X, Y;
        var ev = _mouseP(e), t = _elCsys(this);
        X = ev.x - t.left + this.parentNode.scrollLeft;
        Y = ev.y - t.top + this.parentNode.scrollTop;
        This.addNode(new Date().getTime().toString(), { name: oname, left: X, top: Y, type: This.$nowType });
        This.$max++;
    });

    this.$draw = null;//画矢量线条的容器
    this._initDraw("draw_" + this.$id, width, height);
    this.$group = null;//画区域块（泳道）的容器
    if (property.haveGroup)
        this._initGroup(width, height);
    //为了节点而增加的一些集体绑定
    this._initWorkForNode();

    //一些基本的元素事件，这些事件可直接通过this访问对象本身
    //当操作某个单元（结点/线）被由不选中变成选中时，触发的方法，返回FALSE可阻止选中事件的发生
    //格式function(id,type)：id是单元的唯一标识ID,type是单元的种类,有"node","line"两种取值,"area"不支持被选中
    this.onItemFocus = null;
    //当操作某个单元（结点/线）被由选中变成不选中时，触发的方法，返回FALSE可阻止取消选中事件的发生
    //格式function(id，type)：id是单元的唯一标识ID,type是单元的种类,有"node","line"两种取值,"area"不支持被取消选中
    this.onItemBlur = null;
    //当用重色标注某个结点/转换线时触发的方法，返回FALSE可阻止重定大小/造型事件的发生
    //格式function(id，type，mark)：id是单元的唯一标识ID,type是单元类型（"node"结点,"line"转换线），mark为布尔值,表示是要标注TRUE还是取消标注FALSE
    this.onItemMark = null;
    //当操作某个单元（结点/线/区域块）被双击时，触发的方法，返回FALSE可阻止取消原来双击事件（双击后直接编辑）的发生
    //格式function(id，type)：id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值
    this.onItemDbClick = null;
    //当操作某个单元（结点/线/区域块）被右键点击时，触发的方法，返回FALSE可阻止取消原来右击事件（一般是浏览器默认的右键菜单）的发生
    //格式function(id，type)：id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值
    this.onItemRightClick = null;

    if (this.$editable) {
        //绑定当结点/线/分组块的一些操作事件,这些事件可直接通过this访问对象本身
        //当操作某个单元（结点/线/分组块）被添加时，触发的方法，返回FALSE可阻止添加事件的发生
        //格式function(id，type,json)：id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值,json即addNode,addLine或addArea方法的第二个传参json.
        this.onItemAdd = null;
        //当操作某个单元（结点/线/分组块）被删除时，触发的方法，返回FALSE可阻止删除事件的发生
        //格式function(id，type)：id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值
        this.onItemDel = null;
        //当操作某个单元（结点/分组块）被移动时，触发的方法，返回FALSE可阻止移动事件的发生
        //格式function(id，type,left,top)：id是单元的唯一标识ID,type是单元的种类,有"node","area"两种取值，线line不支持移动,left是新的左边距坐标，top是新的顶边距坐标
        this.onItemMove = null;
        //当操作某个单元（结点/线/分组块）被重命名时，触发的方法，返回FALSE可阻止重命名事件的发生
        //格式function(id,name,type)：id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值,name是新的名称
        this.onItemRename = null;
        //当操作某个单元（结点/分组块）被重定义大小或造型时，触发的方法，返回FALSE可阻止重定大小/造型事件的发生
        //格式function(id，type,width,height)：id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值;width是新的宽度,height是新的高度
        this.onItemResize = null;
        //当移动某条折线中段的位置，触发的方法，返回FALSE可阻止重定大小/造型事件的发生
        //格式function(id，M)：id是单元的唯一标识ID,M是中段的新X(或Y)的坐标
        this.onLineMove = null;
        //当变换某条连接线的类型，触发的方法，返回FALSE可阻止重定大小/造型事件的发生
        //格式function(id，type)：id是单元的唯一标识ID,type是连接线的新类型,"sl":直线,"lr":中段可左右移动的折线,"tb":中段可上下移动的折线
        this.onLineSetType = null;
        //当变换某条连接线的端点变更连接的结点时，触发的方法，返回FALSE可阻止重定大小/造型事件的发生
        //格式function(id，newStart,newEnd)：id是连线单元的唯一标识ID,newStart,newEnd分别是起始结点的ID和到达结点的ID
        this.onLinePointMove = null;
        this._initExpendFunc();//初始化手动扩展工作区宽高的功能
        //对节点、区域块进行移动或者RESIZE时用来显示的遮罩层
        this.$ghost = $("<div class='rs_ghost'></div>").attr({ "unselectable": "on", "onselectstart": 'return false', "onselect": 'document.selection.empty()' });
        this.$bgDiv.append(this.$ghost);
        this._initEditFunc(property.useOperStack);
    }
};

GooFlow.prototype = {
    useSVG: "", //浏览器是否能用SVG？
    _getSvgMarker: function (id, color) {
        var m = document.createElementNS("http://www.w3.org/2000/svg", "marker");
        m.setAttribute("id", id);
        m.setAttribute("viewBox", "0 0 6 6");
        m.setAttribute("refX", '5');
        m.setAttribute("refY", '3');
        m.setAttribute("markerUnits", "strokeWidth");
        m.setAttribute("markerWidth", '6');
        m.setAttribute("markerHeight", '6');
        m.setAttribute("orient", "auto");
        var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
        path.setAttribute("d", "M 0 0 L 6 3 L 0 6 z");
        path.setAttribute("fill", color);
        path.setAttribute("stroke-width", '0');
        m.appendChild(path);
        return m;
    },
    //初始化连线层
    _initDraw: function (id, width, height) {
        if (GooFlow.prototype.useSVG !== "") {
            this.$draw = document.createElementNS("http://www.w3.org/2000/svg", "svg");//可创建带有指定命名空间的元素节点
            this.$workArea.prepend(this.$draw);
            var defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
            this.$draw.appendChild(defs);
            defs.appendChild(GooFlow.prototype._getSvgMarker("arrow1", GooFlow.color.line));
            defs.appendChild(GooFlow.prototype._getSvgMarker("arrow2", GooFlow.color.mark));
            defs.appendChild(GooFlow.prototype._getSvgMarker("arrow3", GooFlow.color.mark));
        }
        else {
            this.$draw = document.createElement("v:group");
            this.$draw.coordsize = width + "," + height;
            this.$workArea.prepend("<div class='GooFlow_work_vml' style='position:relative;width:" + width + "px;height:" + height + "px'></div>");
            this.$workArea.children("div")[0].insertBefore(this.$draw, null);
        }
        this.$draw.id = id;
        this.$draw.style.width = width + "px";
        this.$draw.style.height = height + "px";

        //绑定连线的点击选中以及双击编辑事件
        var tmpClk = null;
        if (GooFlow.prototype.useSVG !== "") tmpClk = "g";
        else tmpClk = "PolyLine";
        //绑定选中事件
        $(this.$draw).on("click", tmpClk, { inthis: this }, function (e) {
            e.data.inthis.focusItem(this.id, true);
        });
        if (!this.$editable) return;

        //绑定右键事件
        $(this.$draw).on("contextmenu", tmpClk, { inthis: this }, function (e) {
            var This = e.data.inthis;
            if (typeof This.onItemRightClick === 'function' && This.onItemRightClick(this.id, "line") === false) {
                window.event ? window.event.returnValue = false : e.preventDefault();
                return false;
            }
        });
        $(this.$draw).on("dblclick", tmpClk, { inthis: this }, function (e) {
            var This = e.data.inthis;
            if (typeof This.onItemDbClick === 'function' && This.onItemDbClick(this.id, "line") === false) return;
            var oldTxt, x, y, from, to;
            if (GooFlow.prototype.useSVG !== "") {
                oldTxt = this.childNodes[2].textContent;
                from = this.getAttribute("from").split(",");
                to = this.getAttribute("to").split(",");
            } else {
                oldTxt = this.childNodes[1].innerHTML;
                var n = this.getAttribute("fromTo").split(",");
                from = [n[0], n[1]];
                to = [n[2], n[3]];
            }
            if (This.$lineData[this.id].type === "lr") {
                from[0] = This.$lineData[this.id].M * This.$scale;
                to[0] = from[0];
            }
            else if (This.$lineData[this.id].type === "tb") {
                from[1] = This.$lineData[this.id].M * This.$scale;
                to[1] = from[1];
            }
            x = (parseInt(from[0], 10) + parseInt(to[0], 10)) / 2 - 64;
            y = (parseInt(from[1], 10) + parseInt(to[1], 10)) / 2 - 18;
            var t = This.t;//t=_elCsys(This.$workArea[0]);
            This.$textArea.val(oldTxt).css({
                display: "block", width: 130, height: 26,
                left: t.left + x - This.$workArea[0].parentNode.scrollLeft,
                top: t.top + y - This.$workArea[0].parentNode.scrollTop
            }).data("id", This.$focus).focus();
            This.$workArea.parent().one("mousedown", function (e) {
                if (e.button === 2) return false;
                This.setName(This.$textArea.data("id"), This.$textArea.val(), "line");
                This.$textArea.val("").removeData("id").hide();
            });
        });
    },
    //初始化区域块（泳道）层
    _initGroup: function (width, height) {
        this.$group = $("<div class='GooFlow_work_group' style='width:" + width + "px;height:" + height + "px'></div>");//存放背景区域的容器
        this.$workArea.prepend(this.$group);
        if (!this.$editable) return;

        //绑定右键事件
        this.$group.on("contextmenu", ".GooFlow_area", { inthis: this }, function (e) {
            var This = e.data.inthis;
            if (typeof This.onItemRightClick === 'function' && This.onItemRightClick(this.id, "area") === false) {
                window.event ? window.event.returnValue = false : e.preventDefault();
                return false;
            }
        });
        //区域划分框操作区的事件绑定
        this.$group.on("mousedown", { inthis: this }, function (e) {//绑定RESIZE功能以及移动功能
            if (e.button === 2) return false;
            var This = e.data.inthis;
            if (This.$nowType !== "group") return;
            if (!e) e = window.event;
            var cursor = $(e.target).css("cursor");
            var id = e.target.parentNode;
            switch (cursor) {
                case "nw-resize": id = id.parentNode; break;
                case "w-resize": id = id.parentNode; break;
                case "n-resize": id = id.parentNode; break;
                case "move": break;
                default: return;
            }
            id = id.id;

            var ev = _mouseP(e), t = This.t;//t=_elCsys(This.$workArea[0]);

            var X, Y, vX, vY;
            X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
            Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
            if (cursor !== "move") {
                This.$ghost.css({
                    display: "block",
                    width: This.$areaData[id].width * This.$scale + "px",
                    height: This.$areaData[id].height * This.$scale + "px",
                    top: This.$areaData[id].top * This.$scale + t.top - This.$workArea[0].parentNode.scrollTop + "px",
                    left: This.$areaData[id].left * This.$scale + t.left - This.$workArea[0].parentNode.scrollLeft + "px",
                    cursor: cursor
                });
                vX = (This.$areaData[id].left * This.$scale + This.$areaData[id].width * This.$scale) - X;
                vY = (This.$areaData[id].top * This.$scale + This.$areaData[id].height * This.$scale) - Y;
            }
            else {
                vX = X - This.$areaData[id].left * This.$scale;
                vY = Y - This.$areaData[id].top * This.$scale;
            }
            var isMove = false;
            This.$ghost.css("cursor", cursor);
            document.onmousemove = function (e) {
                if (!e) e = window.event;
                var ev = _mouseP(e);
                if (cursor !== "move") {
                    X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft - This.$areaData[id].left * This.$scale + vX;
                    Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop - This.$areaData[id].top * This.$scale + vY;
                    if (X < 200 * This.$scale) X = 200 * This.$scale;
                    if (Y < 100 * This.$scale) Y = 100 * This.$scale;
                    switch (cursor) {
                        case "nw-resize": This.$ghost.css({ width: X + "px", height: Y + "px" }); break;
                        case "w-resize": This.$ghost.css({ width: X + "px" }); break;
                        case "n-resize": This.$ghost.css({ height: Y + "px" }); break;
                    }
                }
                else {
                    if (This.$ghost.css("display") === "none") {
                        This.$ghost.css({
                            display: "block",
                            width: This.$areaData[id].width * This.$scale + "px", height: This.$areaData[id].height * This.$scale + "px",
                            top: This.$areaData[id].top * This.$scale + t.top - This.$workArea[0].parentNode.scrollTop + "px",
                            left: This.$areaData[id].left * This.$scale + t.left - This.$workArea[0].parentNode.scrollLeft + "px", cursor: cursor
                        });
                    }
                    X = ev.x - vX; Y = ev.y - vY;
                    if (X < t.left - This.$workArea[0].parentNode.scrollLeft)
                        X = t.left - This.$workArea[0].parentNode.scrollLeft;
                    else if (X + This.$workArea[0].parentNode.scrollLeft + This.$areaData[id].width * This.$scale > t.left + This.$workArea.width())
                        X = t.left + This.$workArea.width() - This.$workArea[0].parentNode.scrollLeft - This.$areaData[id].width * This.$scale;
                    if (Y < t.top - This.$workArea[0].parentNode.scrollTop)
                        Y = t.top - This.$workArea[0].parentNode.scrollTop;
                    else if (Y + This.$workArea[0].parentNode.scrollTop + This.$areaData[id].height * This.$scale > t.top + This.$workArea.height())
                        Y = t.top + This.$workArea.height() - This.$workArea[0].parentNode.scrollTop - This.$areaData[id].height * This.$scale;
                    This.$ghost.css({ left: X + "px", top: Y + "px" });
                }
                isMove = true;
            };
            document.onmouseup = function () {
                This.$ghost.empty().hide();
                document.onmousemove = null;
                document.onmouseup = null;
                if (!isMove) return;
                if (cursor !== "move")
                    This.resizeArea(id, This.$ghost.outerWidth() / This.$scale, This.$ghost.outerHeight() / This.$scale);
                else
                    This.moveArea(id, (X + This.$workArea[0].parentNode.scrollLeft - t.left) / This.$scale, (Y + This.$workArea[0].parentNode.scrollTop - t.top) / This.$scale);
                return false;
            }
        });
        //绑定修改文字说明功能
        this.$group.on("dblclick", { inthis: this }, function (e) {
            var This = e.data.inthis;
            if (This.$nowType !== "group") return;
            if (!e) e = window.event;
            if (e.target.tagName !== "LABEL") return false;
            var p = e.target.parentNode;
            if (typeof This.onItemDbClick === 'function' && This.onItemDbClick(p.id, "area") === false) return;

            var oldTxt = e.target.innerHTML;
            var x = parseInt(p.style.left, 10) + 18, y = parseInt(p.style.top, 10) + 1;
            var t = This.t;//t=_elCsys(This.$workArea[0]);
            This.$textArea.val(oldTxt).css({
                display: "block", width: 130, height: 26,
                left: t.left + x - This.$workArea[0].parentNode.scrollLeft,
                top: t.top + y - This.$workArea[0].parentNode.scrollTop
            }).data("id", p.id).focus();
            This.$workArea.parent().one("mouseup", function (e) {
                if (e.button === 2) return false;
                if (This.$textArea.css("display") === "block") {
                    This.setName(This.$textArea.data("id"), This.$textArea.val(), "area");
                    This.$textArea.val("").removeData("id").hide();
                }
                return false;
            });
            return false;
        });
        //绑定点击事件
        this.$group.mouseup({ inthis: this }, function (e) {
            var This = e.data.inthis;
            if (This.$textArea.css("display") === "block") {
                This.setName(This.$textArea.data("id"), This.$textArea.val(), "area");
                This.$textArea.val("").removeData("id").hide();
                return false;
            }

            if (This.$nowType !== "group") return;
            if (!e) e = window.event;
            switch ($(e.target).attr("class")) {
                case "rs_close": This.delArea(e.target.parentNode.parentNode.id); return false;//删除该分组区域
                case "bg": return;
            }
            switch (e.target.tagName) {
                case "LABEL": return false;
                case "I"://绑定变色功能
                    var id = e.target.parentNode.id;
                    switch (This.$areaData[id].color) {
                        case "red": This.setAreaColor(id, "yellow"); break;
                        case "yellow": This.setAreaColor(id, "blue"); break;
                        case "blue": This.setAreaColor(id, "green"); break;
                        case "green": This.setAreaColor(id, "red"); break;
                    }
                    return false;
            }
            if (e.data.inthis.$ghost.css("display") === "none") {
                var X, Y;
                var ev = _mouseP(e), t = _elCsys(this);
                X = ev.x - t.left + this.parentNode.parentNode.scrollLeft;
                Y = ev.y - t.top + this.parentNode.parentNode.scrollTop;
                var color = ["red", "yellow", "blue", "green"];
                e.data.inthis.addArea(new Date().getTime(),
                    { name: "area_" + e.data.inthis.$max, left: X / This.$scale, top: Y / This.$scale, color: color[e.data.inthis.$max % 4], width: 200, height: 100 }
                );
                e.data.inthis.$max++;
                return false;
            }
        });
    },
    //初始化节点绘制层
    _initWorkForNode: function () {
        //绑定点击事件
        this.$workArea.on("click", ".GooFlow_item", { inthis: this }, function (e) {
            e.data.inthis.focusItem(this.id, true);
            $(this).removeClass("item_mark");
        });
        //绑定右键事件
        this.$workArea.on("contextmenu", ".GooFlow_item", { inthis: this }, function (e) {
            var This = e.data.inthis;
            if (typeof This.onItemRightClick === 'function' && This.onItemRightClick(this.id, "node") === false) {
                window.event ? window.event.returnValue = false : e.preventDefault();
                return false;
            }
        });

        //绑定双击功能
        var tmpDbClickFunc = function (This) {
            This.$workArea.parent().one("mousedown", function (e) {
                if (e.button === 2) return false;
                This.setName(This.$textArea.data("id"), This.$textArea.val(), "node");
                This.$textArea.val("").removeData("id").hide();
            });
        };
        this.$workArea.on("dblclick", ".ico", { inthis: this }, function (e) {
            var id = $(this).parents(".GooFlow_item").attr("id");
            var This = e.data.inthis;
            if (typeof This.onItemDbClick === 'function' && This.onItemDbClick(id, "node") === false) return false;
        });
        //绑定双击(包括双击编辑)事件
        this.$workArea.on("dblclick", ".GooFlow_item > .span", { inthis: this }, function (e) {
            var id = this.parentNode.id;
            var This = e.data.inthis;
            if (typeof This.onItemDbClick === 'function' && This.onItemDbClick(id, "node") === false) return false;
            if (!This.$editable) return;
            var oldTxt = this.innerHTML;
            var t = This.t;//t=_elCsys(This.$workArea[0]);
            This.$textArea.val(oldTxt).css({
                display: "block", height: $(this).height() + 6, width: 100,
                left: t.left + This.$nodeData[id].left * This.$scale - This.$workArea[0].parentNode.scrollLeft - 26,
                top: t.top + This.$nodeData[id].top * This.$scale - This.$workArea[0].parentNode.scrollTop + 26
            })
                .data("id", This.$focus).focus();
            tmpDbClickFunc(This);
        });
        this.$workArea.on("dblclick", ".ico + td", { inthis: this }, function (e) {
            var id = $(this).parents(".GooFlow_item").attr("id");
            var This = e.data.inthis;
            if (typeof This.onItemDbClick === 'function' && This.onItemDbClick(id, "node") === false) return false;
            if (!This.$editable) return;
            var oldTxt = this.childNodes[0].innerHTML;
            var t = This.t;//t=_elCsys(This.$workArea[0]);
            This.$textArea.val(oldTxt).css({
                display: "block", width: $(this).width() + 26, height: $(this).height() + 6,
                left: t.left + 26 + This.$nodeData[id].left * This.$scale - This.$workArea[0].parentNode.scrollLeft,
                top: t.top + 2 + This.$nodeData[id].top * This.$scale - This.$workArea[0].parentNode.scrollTop
            })
                .data("id", This.$focus).focus();
            tmpDbClickFunc(This);
        });
        if (!this.$editable) return;

        //以下是工作区为编辑模式时才绑定的事件
        //绑定用鼠标移动事件
        this.$workArea.on("mousedown", ".ico", { inthis: this }, function (e) {
            if (!e) e = window.event;
            if (e.button === 2) return false;
            var This = e.data.inthis;
            if (This.$nowType === "direct" || This.$nowType === "dashed") return;
            var Dom = $(this).parents(".GooFlow_item");
            var id = Dom.attr("id");
            This.focusItem(id, true);

            var ev = _mouseP(e), t = This.t;//t=_elCsys(This.$workArea[0]);

            Dom.children("table").clone().prependTo(This.$ghost);
            var X, Y;
            X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
            Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
            var vX = X - This.$nodeData[id].left * This.$scale, vY = Y - This.$nodeData[id].top * This.$scale;
            var isMove = false;
            document.onmousemove = function (e) {
                if (!e) e = window.event;
                var ev = _mouseP(e);
                if (X === ev.x - vX && Y === ev.y - vY) return false;
                X = ev.x - vX; Y = ev.y - vY;

                if (isMove && This.$ghost.css("display") === "none") {
                    This.$ghost.css({
                        display: "block",
                        width: This.$nodeData[id].width * This.$scale + "px", height: This.$nodeData[id].height * This.$scale + "px",
                        top: This.$nodeData[id].top * This.$scale + t.top - This.$workArea[0].parentNode.scrollTop + "px",
                        left: This.$nodeData[id].left * This.$scale + t.left - This.$workArea[0].parentNode.scrollLeft + "px",
                        cursor: "move"
                    });
                }

                if (X < t.left - This.$workArea[0].parentNode.scrollLeft)
                    X = t.left - This.$workArea[0].parentNode.scrollLeft;
                else if (X + This.$workArea[0].parentNode.scrollLeft + This.$nodeData[id].width * This.$scale > t.left + This.$workArea.width())
                    X = t.left + This.$workArea.width() - This.$workArea[0].parentNode.scrollLeft - This.$nodeData[id].width * This.$scale;
                if (Y < t.top - This.$workArea[0].parentNode.scrollTop)
                    Y = t.top - This.$workArea[0].parentNode.scrollTop;
                else if (Y + This.$workArea[0].parentNode.scrollTop + This.$nodeData[id].height * This.$scale > t.top + This.$workArea.height())
                    Y = t.top + This.$workArea.height() - This.$workArea[0].parentNode.scrollTop - This.$nodeData[id].height * This.$scale;
                This.$ghost.css({ left: X + "px", top: Y + "px" });
                isMove = true;
            };
            document.onmouseup = function () {
                if (isMove) This.moveNode(id, (X + This.$workArea[0].parentNode.scrollLeft - t.left) / This.$scale, (Y + This.$workArea[0].parentNode.scrollTop - t.top) / This.$scale);
                This.$ghost.empty().hide();
                document.onmousemove = null;
                document.onmouseup = null;
            }
        });
        //绑定鼠标覆盖/移出事件
        this.$workArea.on("mouseenter", ".GooFlow_item", { inthis: this }, function (e) {
            if ((e.data.inthis.$nowType !== "direct" && e.data.inthis.$nowType !== "dashed") && !document.getElementById("GooFlow_tmp_line")) return;
            $(this).addClass("item_mark").addClass("crosshair").css("border-color", GooFlow.color.mark);
        });
        this.$workArea.on("mouseleave", ".GooFlow_item", { inthis: this }, function (e) {
            if ((e.data.inthis.$nowType !== "direct" && e.data.inthis.$nowType !== "dashed") && !document.getElementById("GooFlow_tmp_line")) return;
            $(this).removeClass("item_mark").removeClass("crosshair");
            if (this.id === e.data.inthis.$focus) {
                $(this).css("border-color", GooFlow.color.line);
            } else {
                $(this).css("border-color", GooFlow.color.node);
            }
        });
        //绑定连线时确定初始点
        this.$workArea.on("mousedown", ".GooFlow_item", { inthis: this }, function (e) {
            if (e.button === 2) return false;
            var This = e.data.inthis;
            if (This.$nowType !== "direct" && This.$nowType !== "dashed") return;
            var ev = _mouseP(e), t = _elCsys(This.$workArea[0]);
            var X, Y;
            X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
            Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
            This.$workArea.data("lineStart", { "x": X, "y": Y, "id": this.id }).css("cursor", "crosshair");
            var line = GooFlow.prototype.drawLine("GooFlow_tmp_line", [X, Y], [X, Y], true, true, 1);
            This.$draw.appendChild(line);
        });
        //绑定连线时确定结束点
        this.$workArea.on("mouseup", ".GooFlow_item", { inthis: this }, function (e) {
            var This = e.data.inthis;
            if ((This.$nowType !== "direct" && This.$nowType !== "dashed") && !This.$mpTo.data("p")) return;
            var lineStart = This.$workArea.data("lineStart");
            var lineEnd = This.$workArea.data("lineEnd");
            if (lineStart && !This.$mpTo.data("p")) {
                var tmp = { from: lineStart.id, to: this.id, name: "" };
                if (This.$nowType === "dashed") {
                    tmp.dash = true;
                }
                This.addLine(new Date().getTime().toString(), tmp);
                This.$max++;
            }
            else {
                if (lineStart) {
                    This.moveLinePoints(This.$focus, lineStart.id, this.id);
                } else if (lineEnd) {
                    This.moveLinePoints(This.$focus, this.id, lineEnd.id);
                }
                if (!This.$nodeData[this.id].marked) {
                    $(this).removeClass("item_mark");
                    if (this.id !== This.$focus) {
                        $(this).css("border-color", GooFlow.color.node);
                    }
                    else {
                        $(this).css("border-color", GooFlow.color.line);
                    }
                }
            }
        });

        //绑定结点的删除功能
        this.$workArea.on("click", ".rs_close", { inthis: this }, function (e) {
            if (!e) e = window.event;
            e.data.inthis.delNode(e.data.inthis.$focus);
            return false;
        });
        //绑定结点的RESIZE功能
        this.$workArea.on("mousedown", ".GooFlow_item > div > div[class!=rs_close]", { inthis: this }, function (e) {
            if (!e) e = window.event;
            if (e.button === 2) return false;
            var cursor = $(this).css("cursor");
            if (cursor === "pointer") { return; }
            var This = e.data.inthis;
            var id = This.$focus;
            This.switchToolBtn("cursor");
            e.cancelBubble = true;
            e.stopPropagation();

            var ev = _mouseP(e), t = This.t;//t=_elCsys(This.$workArea[0]);
            This.$ghost.css({
                display: "block",
                width: This.$nodeData[id].width * This.$scale + "px", height: This.$nodeData[id].height * This.$scale + "px",
                top: This.$nodeData[id].top * This.$scale + t.top - This.$workArea[0].parentNode.scrollTop + "px",
                left: This.$nodeData[id].left * This.$scale + t.left - This.$workArea[0].parentNode.scrollLeft + "px",
                cursor: cursor
            });
            var X, Y;
            X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
            Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
            var vX = (This.$nodeData[id].left * This.$scale + This.$nodeData[id].width * This.$scale) - X;
            var vY = (This.$nodeData[id].top * This.$scale + This.$nodeData[id].height * This.$scale) - Y;
            var isMove = false;
            This.$ghost.css("cursor", cursor);
            document.onmousemove = function (e) {
                if (!e) e = window.event;
                var ev = _mouseP(e);
                X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft - This.$nodeData[id].left * This.$scale + vX;
                Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop - This.$nodeData[id].top * This.$scale + vY;
                if (X < 104 * This.$scale) X = 104 * This.$scale;
                if (Y < 28 * This.$scale) Y = 28 * This.$scale;
                isMove = true;
                switch (cursor) {
                    case "nw-resize": This.$ghost.css({ width: X + "px", height: Y + "px" }); break;
                    case "w-resize": This.$ghost.css({ width: X + "px" }); break;
                    case "n-resize": This.$ghost.css({ height: Y + "px" }); break;
                }
            };
            document.onmouseup = function () {
                document.onmousemove = null;
                document.onmouseup = null;
                This.$ghost.hide();
                if (!isMove) return;
                //if(!e)e=window.event;
                This.resizeNode(id, This.$ghost.outerWidth() / This.$scale, This.$ghost.outerHeight() / This.$scale);
            };
        });
    },
    //加入手动扩展编辑区功能，一次扩展200px
    _initExpendFunc: function () {
        var titleExendRight = GooFlow.remarks.extendRight ? ' title="' + GooFlow.remarks.extendRight + '"' : '';
        var titleExendBottom = GooFlow.remarks.extendBottom ? ' title="' + GooFlow.remarks.extendBottom + '"' : '';
        this.$workArea.append('<div class="Gooflow_extend_right"' + titleExendRight + '></div><div class="Gooflow_extend_bottom"' + titleExendBottom + '></div>');
        this.$workArea.children(".Gooflow_extend_right").on("click", { inthis: this }, function (e) {
            var This = e.data.inthis;
            var w = This.$workArea.width() + This.$workExtendStep;
            var h = This.$workArea.height();
            This.$workArea.css({ width: w + "px" });
            if (GooFlow.prototype.useSVG === "") {
                This.$draw.coordsize = w + "," + h;
            }
            This.$draw.style.width = w + "px";
            if (This.$group != null) {
                This.$group.css({ width: w + "px" });
            }
            var parentDiv = This.$workArea.parent()[0];
            parentDiv.scrollLeft = parentDiv.scrollWidth;
            This.$workArea.parent().css("overflow", "scroll");
            return false;
        });
        this.$workArea.children(".Gooflow_extend_bottom").on("click", { inthis: this }, function (e) {
            var This = e.data.inthis;
            var w = This.$workArea.width();
            var h = This.$workArea.height() + This.$workExtendStep;
            This.$workArea.css({ height: h + "px" });
            if (GooFlow.prototype.useSVG === "") {
                This.$draw.coordsize = w + "," + h;
            }
            This.$draw.style.height = h + "px";
            if (This.$group != null) {
                This.$group.css({ height: h + "px" });
            }
            var parentDiv = This.$workArea.parent()[0];
            parentDiv.scrollTop = parentDiv.scrollHeight;
            This.$workArea.parent().css("overflow", "scroll");
            return false;
        });
    },
    //初始化用来改变连线的连接端点的两个小方块的操作事件
    _initLinePointsChg: function () {
        this.$mpFrom.on("mousedown", { inthis: this }, function (e) {
            var This = e.data.inthis;
            This.switchToolBtn("cursor");
            var ps = This.$mpFrom.data("p").split(",");
            var pe = This.$mpTo.data("p").split(",");
            $(this).hide();
            This.$workArea.data("lineEnd", { "x": pe[0], "y": pe[1], "id": This.$lineData[This.$lineOper.data("tid")].to }).css("cursor", "crosshair");
            var line = GooFlow.prototype.drawLine("GooFlow_tmp_line", [ps[0], ps[1]], [pe[0], pe[1]], true, true, 1);
            This.$draw.appendChild(line);
            return false;
        });
        this.$mpTo.on("mousedown", { inthis: this }, function (e) {
            var This = e.data.inthis;
            This.switchToolBtn("cursor");
            var ps = This.$mpFrom.data("p").split(",");
            var pe = This.$mpTo.data("p").split(",");
            $(this).hide();
            This.$workArea.data("lineStart", { "x": ps[0], "y": ps[1], "id": This.$lineData[This.$lineOper.data("tid")].from }).css("cursor", "crosshair");
            var line = GooFlow.prototype.drawLine("GooFlow_tmp_line", [ps[0], ps[1]], [pe[0], pe[1]], true, true, 1);
            This.$draw.appendChild(line);
            return false;
        });
    },
    //初始化设计器的编辑功能
    _initEditFunc: function (useOperStack) {
        //划线或改线时用的绑定
        this.$workArea.mousemove({ inthis: this }, function (e) {
            var This = e.data.inthis;
            if ((This.$nowType !== "direct" && This.$nowType !== "dashed") && !This.$mpTo.data("p")) return;
            var lineStart = $(this).data("lineStart");
            var lineEnd = $(this).data("lineEnd");
            if (!lineStart && !lineEnd) return;

            var ev = _mouseP(e), t = _elCsys(this);
            var X, Y;
            X = ev.x - t.left + this.parentNode.scrollLeft;
            Y = ev.y - t.top + this.parentNode.scrollTop;
            var line = document.getElementById("GooFlow_tmp_line");
            if (lineStart) {
                if (GooFlow.prototype.useSVG !== "") {
                    line.childNodes[0].setAttribute("d", "M " + lineStart.x + " " + lineStart.y + " L " + X + " " + Y);
                    line.childNodes[1].setAttribute("d", "M " + lineStart.x + " " + lineStart.y + " L " + X + " " + Y);
                    if (line.childNodes[1].getAttribute("marker-end") === 'url("#arrow2")')
                        line.childNodes[1].setAttribute("marker-end", "url(#arrow3)");
                    else line.childNodes[1].setAttribute("marker-end", "url(#arrow2)");
                }
                else line.points.value = lineStart.x + "," + lineStart.y + " " + X + "," + Y;
            } else if (lineEnd) {
                if (GooFlow.prototype.useSVG !== "") {
                    line.childNodes[0].setAttribute("d", "M " + X + " " + Y + " L " + lineEnd.x + " " + lineEnd.y);
                    line.childNodes[1].setAttribute("d", "M " + X + " " + Y + " L " + lineEnd.x + " " + lineEnd.y);
                    if (line.childNodes[1].getAttribute("marker-end") === 'url("#arrow2")')
                        line.childNodes[1].setAttribute("marker-end", "url(#arrow3)");
                    else line.childNodes[1].setAttribute("marker-end", "url(#arrow2)");
                }
                else line.points.value = X + "," + Y + " " + lineEnd.x + "," + lineEnd.y;
            }
        });
        this.$workArea.mouseup({ inthis: this }, function (e) {
            var This = e.data.inthis;
            if ((This.$nowType !== "direct" && This.$nowType !== "dashed") && !This.$mpTo.data("p")) return;
            var tmp = document.getElementById("GooFlow_tmp_line");
            if (tmp) {
                $(this).css("cursor", "auto").removeData("lineStart").removeData("lineEnd");
                This.$mpTo.hide().removeData("p");
                This.$mpFrom.hide().removeData("p");
                This.$draw.removeChild(tmp);
                This.focusItem(This.$focus, false);
            } else {
                This.$lineOper.removeData("tid");
            }
        });

        this.$textArea = $("<textarea></textarea>");
        this.$bgDiv.append(this.$textArea);
        this.$lineMove = $('<div class="GooFlow_linemove" style="display:none"></div>');//操作折线时的移动框
        this.$workArea.append(this.$lineMove);
        this.$lineMove.on("mousedown", { inthis: this }, function (e) {
            if (e.button === 2) return false;
            var lm = $(this);
            lm.css({ "background-color": "#333" });
            var This = e.data.inthis;
            var ev = _mouseP(e), t = _elCsys(This.$workArea[0]);
            var X, Y;
            X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
            Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
            var p = This.$lineMove.position();
            var vX = X - p.left, vY = Y - p.top;
            var isMove = false;
            document.onmousemove = function (e) {
                if (!e) e = window.event;
                var ev = _mouseP(e);
                //var ps=This.$lineMove.position();
                X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
                Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
                if (This.$lineMove.data("type") === "lr") {
                    X = X - vX;
                    if (X < 0) X = 0;
                    else if (X > This.$workArea.width())
                        X = This.$workArea.width();
                    This.$lineMove.css({ left: X + "px" });
                }
                else if (This.$lineMove.data("type") === "tb") {
                    Y = Y - vY;
                    if (Y < 0) Y = 0;
                    else if (Y > This.$workArea.height())
                        Y = This.$workArea.height();
                    This.$lineMove.css({ top: Y + "px" });
                }
                isMove = true;
            };
            document.onmouseup = function () {
                if (isMove) {
                    var p = This.$lineMove.position();
                    if (This.$lineMove.data("type") === "lr")
                        This.setLineM(This.$lineMove.data("tid"), (p.left + 3) / This.$scale);
                    else if (This.$lineMove.data("type") === "tb")
                        This.setLineM(This.$lineMove.data("tid"), (p.top + 3) / This.$scale);
                }
                This.$lineMove.css({ "background-color": "transparent" });
                if (This.$focus === This.$lineMove.data("tid")) {
                    This.focusItem(This.$lineMove.data("tid"));
                }
                document.onmousemove = null;
                document.onmouseup = null;
            };
        });

        //选定一条转换线后出现的浮动操作栏，有改变线的样式和删除线等按钮。
        this.$lineOper = $("<div class='GooFlow_line_oper' style='display:none'><i class='b_l1'></i><i class='b_l2'></i><i class='b_l3'></i><i class='b_x'></i></div>");//选定线时显示的操作框
        this.$workArea.parent().append(this.$lineOper);
        this.$lineOper.on("click", { inthis: this }, function (e) {
            if (!e) e = window.event;
            if (e.target.tagName !== "I") return;
            var This = e.data.inthis;
            var id = $(this).data("tid");
            switch ($(e.target).attr("class")) {
                case "b_x":
                    This.delLine(id);
                    this.style.display = "none"; break;
                case "b_l1":
                    This.setLineType(id, "lr"); break;
                case "b_l2":
                    This.setLineType(id, "tb"); break;
                case "b_l3":
                    This.setLineType(id, "sl"); break;
            }
        });
        //新增移动线两个端点至新的结点功能移动功能，这里要提供移动用的DOM
        this.$mpFrom = $("<div class='GooFlow_line_mp' style='display:none'></div>");
        this.$mpTo = $("<div class='GooFlow_line_mp' style='display:none'></div>");
        this.$workArea.append(this.$mpFrom).append(this.$mpTo);
        this._initLinePointsChg();

        if (useOperStack) {//如果要使用堆栈记录操作并提供“撤销/重做”的功能,只在编辑状态下有效
            this.$undoStack = [];
            this.$redoStack = [];
            this.$isUndo = 0;
            ///////////////以下是构造撤销操作/重做操作的方法
            //检查撤销栈与重做栈处理好头部按钮的显示
            this._checkStack = function (type) {
                if (this.$head === null) return;
                if (!type || type === 'undo') {
                    if (this.$undoStack.length === 0) {
                        this.$head.find(".ico_undo").parent().addClass("a_disabled");
                    } else {
                        this.$head.find(".ico_undo").parent().removeClass("a_disabled");
                    }
                }
                if (!type || type === 'redo') {
                    if (this.$redoStack.length === 0) {
                        this.$head.find(".ico_redo").parent().addClass("a_disabled");
                    } else {
                        this.$head.find(".ico_redo").parent().removeClass("a_disabled");
                    }
                }
            };
            //为了节省浏览器内存空间,undo/redo中的操作缓存栈,最多只可放50步操作;超过50步时,将自动删掉最旧的一个缓存
            this.pushOper = function (funcName, paras) {
                if (this.$isUndo === 1) {
                    this.$redoStack.push([funcName, paras]);
                    this.$isUndo = 0;
                    if (this.$redoStack.length > 50) this.$redoStack.shift();
                    this._checkStack('redo');
                } else {
                    this.$undoStack.push([funcName, paras]);
                    if (this.$undoStack.length > 50) this.$undoStack.shift();
                    if (this.$isUndo === 0) {
                        this.$redoStack.splice(0, this.$redoStack.length);
                    }
                    this.$isUndo = 0;
                    this._checkStack();
                }
            };
            //将外部的方法加入到GooFlow对象的事务操作堆栈中,在过后的undo/redo操作中可以进行控制，一般用于对流程图以外的附加信息进行编辑的事务撤销/重做控制；
            //传参func为要执行方法对象,jsonPara为外部方法仅有的一个面向字面的JSON传参,由JSON对象带入所有要传的信息；
            //提示:为了让外部方法能够被UNDO/REDO,需要在编写这些外部方法实现时,加入对该方法执行后效果回退的另一个执行方法的pushExternalOper
            this.pushExternalOper = function (func, jsonPara) {
                this.pushOper("externalFunc", [func, jsonPara]);
            };
            //撤销上一步操作
            this.undo = function () {
                if (this.$undoStack.length === 0) return;
                this.blurItem();
                var tmp = this.$undoStack.pop();
                this.$isUndo = 1;
                if (tmp[0] === "externalFunc") {
                    tmp[1][0](tmp[1][1]);
                }
                else {
                    //传参的数量,最小0个最多12个.
                    this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3], tmp[1][4], tmp[1][5],
                        tmp[1][6], tmp[1][7], tmp[1][8], tmp[1][9], tmp[1][10], tmp[1][11]);
                }
                this._checkStack();
            };
            //重做最近一次被撤销的操作
            this.redo = function () {
                if (this.$redoStack.length === 0) return;
                this.blurItem();
                var tmp = this.$redoStack.pop();
                this.$isUndo = 2;
                if (tmp[0] === "externalFunc") {
                    tmp[1][0](tmp[1][1]);
                }
                else {
                    //传参的数量,最小0个最多12个.
                    this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3], tmp[1][4], tmp[1][5],
                        tmp[1][6], tmp[1][7], tmp[1][8], tmp[1][9], tmp[1][10], tmp[1][11]);
                }
                this._checkStack();
            };
        }
        $(document).keydown({ inthis: this }, function (e) {
            //绑定键盘操作
            var This = e.data.inthis;
            if (This.$focus === "") return;
            switch (e.keyCode) {
                case 46://删除
                    This.delNode(This.$focus, true);
                    This.delLine(This.$focus);
                    break;
            }
        });
    },

    //对头部栏自定义按钮的事件绑定，用户可用来对另行加入的头部按钮自定义功能
    //传参为json结构，key为按钮的类型名(需另行写好'ico_'+按钮类型名的样式类定义)，value为相关事件的方法实现定义
    bindHeadBtnEvent: function (funcs) {
        if (this.$head != null)
            this.$headBtnEvents = funcs;
    },
    //每一种类型结点及其按钮的说明文字
    setNodeRemarks: function (remark) {
        if (this.$tool == null) return;
        this.$tool.children("a").each(function () {
            try {
                this.title = remark[$(this).attr("id").split("btn_")[1]];
            } catch (e) { }
        });
    },
    //(当有顶部工具栏按钮组时)设定顶部工具栏按钮的说明文字
    setHeadToolsRemarks: function (remark) {
        if (this.$head == null) return;
        this.$head.children("a").each(function () {
            try {
                this.title = remark[$(this).children("i").attr("class").split('ico_')[1]];
            } catch (e) { }
        });
    },
    //设定扩展工作区宽高的长条按钮的说明文字
    setExtWorkRemarks: function (remark) {
        this.$workArea.children(".Gooflow_extend_right").attr("title", remark.extendRight);
        this.$workArea.children(".Gooflow_extend_bottom").attr("title", remark.extendBottom);
    },

    //切换左边工具栏按钮,传参TYPE表示切换成哪种类型的按钮
    switchToolBtn: function (type) {
        if (this.$tool != null) {
            this.$tool.children("#" + this.$id + "_btn_" + this.$nowType.split(" ")[0]).attr("class", "GooFlow_tool_btn");
        }
        if (this.$nowType === "group") {
            this.$workArea.prepend(this.$group);
            for (var k in this.$areaDom) this.$areaDom[k].addClass("lock").children("div:eq(1)").css("display", "none");
        }
        this.$nowType = type;
        if (this.$tool != null) {
            this.$tool.children("#" + this.$id + "_btn_" + type.split(" ")[0]).attr("class", "GooFlow_tool_btndown");
        }
        if (this.$nowType === "group") {
            this.blurItem();
            this.$workArea.append(this.$group);
            for (var key in this.$areaDom) this.$areaDom[key].removeClass("lock").children("div:eq(1)").css("display", "");
        } else if (this.$nowType === "direct" || this.$nowType === "dashed") {
            this.blurItem();
        }
        if (this.$textArea && this.$textArea.css("display") === "none") this.$textArea.removeData("id").val("").hide();
    },

    //获取结点/连线/分组区域的详细信息
    getItemInfo: function (id, type) {
        switch (type) {
            case "node": return this.$nodeData[id] || null;
            case "line": return this.$lineData[id] || null;
            case "area": return this.$areaData[id] || null;
        }
    },
    //取消所有结点/连线被选定的状态
    blurItem: function () {
        if (this.$focus !== "") {
            var jq = $("#" + this.$focus);
            if (jq.prop("tagName") === "DIV") {
                if (typeof this.onItemBlur === 'function' && this.onItemBlur(this.$focus, "node") === false) return false;
                jq.removeClass("item_focus").children("div:eq(0)").css("display", "none");
                if (this.$nodeData[this.$focus].marked) {
                    jq.addClass("item_mark").css("border-color", GooFlow.color.mark);
                }
            }
            else {
                if (typeof this.onItemBlur === 'function' && this.onItemBlur(this.$focus, "line") === false) return false;
                if (GooFlow.prototype.useSVG !== "") {
                    if (!this.$lineData[this.$focus].marked) {
                        jq[0].childNodes[1].setAttribute("stroke", GooFlow.color.line);
                        jq[0].childNodes[1].setAttribute("marker-end", "url(#arrow1)");
                    }
                }
                else {
                    if (!this.$lineData[this.$focus].marked) {
                        jq[0].strokeColor = GooFlow.color.line;
                    }
                }
                if (this.$editable) {
                    this.$lineMove.hide().removeData("type").removeData("tid");
                    this.$lineOper.hide().removeData("tid");
                    this.$mpFrom.hide().removeData("p");
                    this.$mpTo.hide().removeData("p");
                }
            }
        }
        this.$focus = "";
        return true;
    },
    //选定某个结点/转换线 bool:TRUE决定了要触发选中事件，FALSE则不触发选中事件，多用在程序内部调用。
    focusItem: function (id, bool) {
        var jq = $("#" + id);
        if (jq.length === 0) return;
        if (!this.blurItem()) return;//先执行"取消选中",如果返回FLASE,则也会阻止选定事件继续进行.
        if (bool && typeof this.onItemFocus === 'function' && this.onItemFocus(id, "node") === false) return;
        this.$focus = id;
        if (jq.prop("tagName") === "DIV") {
            jq.addClass("item_focus");
            if (GooFlow.color.line) {
                jq.css("border-color", GooFlow.color.line);
            }
            if (this.$editable) jq.children("div:eq(0)").css("display", "block");
            //this.$workArea.append(jq);
        } else {//如果是连接线
            if (GooFlow.prototype.useSVG !== "") {
                jq[0].childNodes[1].setAttribute("stroke", GooFlow.color.mark);
                jq[0].childNodes[1].setAttribute("marker-end", "url(#arrow2)");
            }
            else {
                jq[0].strokeColor = GooFlow.color.mark;
            }
            if (!this.$editable) return;
            var x, y, from, to, n;
            if (GooFlow.prototype.useSVG !== "") {
                from = jq.attr("from").split(",");
                to = jq.attr("to").split(",");
                n = [from[0], from[1], to[0], to[1]];
            } else {
                n = jq[0].getAttribute("fromTo").split(",");
                from = [n[0], n[1]];
                to = [n[2], n[3]];
            }
            from[0] = parseInt(from[0], 10);
            from[1] = parseInt(from[1], 10);
            to[0] = parseInt(to[0], 10);
            to[1] = parseInt(to[1], 10);
            //var t=_elCsys(this.$workArea[0]);
            if (this.$lineData[id].type === "lr") {
                from[0] = this.$lineData[id].M * this.$scale;
                to[0] = from[0];

                this.$lineMove.css({
                    width: "5px", height: (to[1] - from[1]) * (to[1] > from[1] ? 1 : -1) + "px",
                    left: from[0] - 3 + "px",
                    top: (to[1] > from[1] ? from[1] : to[1]) + 1 + "px",
                    cursor: "e-resize", display: "block"
                }).data({ "type": "lr", "tid": id });
            }
            else if (this.$lineData[id].type === "tb") {
                from[1] = this.$lineData[id].M * this.$scale;
                to[1] = from[1];
                this.$lineMove.css({
                    width: (to[0] - from[0]) * (to[0] > from[0] ? 1 : -1) + "px", height: "5px",
                    left: (to[0] > from[0] ? from[0] : to[0]) + 1 + "px",
                    top: from[1] - 3 + "px",
                    cursor: "s-resize", display: "block"
                }).data({ "type": "tb", "tid": id });
            }
            x = (from[0] + to[0]) / 2 - 40;
            y = (from[1] + to[1]) / 2 + 4;
            this.$lineOper.css({ display: "block", left: x + "px", top: y + "px" }).data("tid", id);
            if (this.$editable) {
                this.$mpFrom.css({ display: "block", left: n[0] - 4 + "px", top: n[1] - 4 + "px" }).data("p", n[0] + "," + n[1]);
                this.$mpTo.css({ display: "block", left: n[2] - 4 + "px", top: n[3] - 4 + "px" }).data("p", n[2] + "," + n[3]);
            }
            this.$draw.appendChild(jq[0]);
        }

        this.switchToolBtn("cursor");
    },
    //传入一个节点的ID，判断在图中的哪个区域组(泳道)的范围内
    _node2Area: function (nodeId) {
        if (this.$group === null) return;
        var node = this.$nodeData[nodeId];
        var lane = false;
        for (var key in this.$areaData) {
            var area = this.$areaData[key];
            if (node.left >= area.left && node.left < area.left + area.width &&
                node.top >= area.top && node.top < area.top + area.height
            ) {
                node.areaId = key;
                lane = true;
                break;
            }
        }
        if (!lane) { delete node.areaId; } //不属于任何区域组(泳道)的情况
    },
    //增加一个流程结点,传参为一个JSON,有id,name,top,left,width,height,type(结点类型)等属性
    addNode: function (id, json) {
        if (json.id == undefined) {
            $.extend(json, { id: id });
        }
        //console.log(json);
        var oname = json.name;
        if (typeof this.onItemAdd === 'function' && this.onItemAdd(id, "node", json) === false) return;
        if (this.$undoStack && this.$editable) {
            this.pushOper("delNode", [id]);
        }
        var mark = json.marked ? " item_mark" : "";
        if (json.type.indexOf(" round") < 0) {
            if (!json.width || json.width < 104) json.width = 104;
            if (!json.height || json.height < 26) json.height = 26;
            if (!json.top || json.top < 0) json.top = 0;
            if (!json.left || json.left < 0) json.left = 0;

            this.$nodeDom[id] = $("<div class='GooFlow_item" + mark + "' id='" + id + "' style='top:" + json.top * this.$scale + "px;left:" + json.left * this.$scale + "px'><table cellspacing='1' style='width:" + (json.width * this.$scale - 2) + "px;height:" + (json.height * this.$scale - 2) + "px;'><tr><td class='ico'><i class='ico_" + json.type + "'></i></td><td><div>" + oname+ "</div></td></tr></table><div style='display:none'><div class='rs_bottom'></div><div class='rs_right'></div><div class='rs_rb'></div><div class='rs_close'></div></div></div>");
        }
        else {
            json.width = 26; json.height = 26;
            this.$nodeDom[id] = $("<div class='GooFlow_item item_round" + mark + "' id='" + id + "' style='top:" + json.top * this.$scale + "px;left:" + json.left * this.$scale + "px'><table cellspacing='0' style='width:" + (json.width * this.$scale - 2) + "px;height:" + (json.height * this.$scale - 2) + "px;'><tr><td class='ico'><i class='ico_" + json.type + "'></i></td></tr></table><div  style='display:none'><div class='rs_close'></div></div><div class='span'>" + oname + "</div></div>");
        }
        if (GooFlow.color.node) {
            if (json.type.indexOf(" mix") > -1) {
                this.$nodeDom[id].css({ "background-color": GooFlow.color.mix, "border-color": GooFlow.color.mix });
                if (GooFlow.color.mixFont) {
                    this.$nodeDom[id].find("td:eq(1)").css("color", GooFlow.color.mixFont);
                    this.$nodeDom[id].find(".span").css("color", GooFlow.color.mixFont);
                }
            } else {
                this.$nodeDom[id].css({ "background-color": GooFlow.color.node, "border-color": GooFlow.color.node });
            }
            if (mark && GooFlow.color.mark) {
                this.$nodeDom[id].css({ "border-color": GooFlow.color.mark });
            }
        }
        if (json.type.indexOf(" mix") > -1) {
            this.$nodeDom[id].addClass("item_mix");
        }

        var ua = navigator.userAgent.toLowerCase();
        if (ua.indexOf('msie') !== -1 && ua.indexOf('8.0') !== -1)
            this.$nodeDom[id].css("filter", "progid:DXImageTransform.Microsoft.Shadow(color=#94AAC2,direction=135,strength=2)");
        this.$workArea.append(this.$nodeDom[id]);
        this.$nodeData[id] = json;
        ++this.$nodeCount;
        if (this.$editable) {
            this.$nodeData[id].alt = true;
            this._node2Area(id);
            if (this.$deletedItem[id]) delete this.$deletedItem[id];//在回退删除操作时,去掉该元素的删除记录
        }
    },
    //移动结点到一个新的位置
    moveNode: function (id, left, top) {
        if (!this.$nodeData[id]) return;
        if (typeof this.onItemMove === 'function' && this.onItemMove(id, "node", left, top) === false) return;
        if (this.$undoStack) {
            var paras = [id, this.$nodeData[id].left, this.$nodeData[id].top];
            this.pushOper("moveNode", paras);
        }
        if (left < 0) left = 0;
        if (top < 0) top = 0;
        $("#" + id).css({ left: left * this.$scale + "px", top: top * this.$scale + "px" });
        this.$nodeData[id].left = left;
        this.$nodeData[id].top = top;
        //重画转换线
        this.resetLines(id, this.$nodeData[id]);
        if (this.$editable) {
            this.$nodeData[id].alt = true;
            this._node2Area(id);
        }
    },
    //设置结点/连线/分组区域的文字信息
    setName: function (id, name, type, setInfo) {
        var oldName;
        if (type === "node") {//如果是结点
            this.$nodeData[id].setInfo = setInfo;
            if (!this.$nodeData[id]) return;
            if (this.$nodeData[id].name === name) return;
            if (typeof this.onItemRename === 'function' && this.onItemRename(id, name, "node") === false) return;
            oldName = this.$nodeData[id].name;
            this.$nodeData[id].name = name;
            if (this.$nodeData[id].type.indexOf("round") > 1) {
                this.$nodeDom[id].children(".span").text(name);
            }
            else {
                this.$nodeDom[id].find("td:eq(1)").children("div").text(name);

                var width = this.$nodeDom[id].outerWidth();
                var height = this.$nodeDom[id].outerHeight();
                if (this.$nodeData[id].width !== width || this.$nodeData[id].height !== height) {
                    this.$nodeDom[id].children("table").css({ width: width - 2 + "px", height: height - 2 + "px" });
                    if (this.$undoStack) {
                        var para = [id, this.$nodeData[id].width, this.$nodeData[id].height];
                        this.pushOper("resizeNode", para);
                    }
                    this.$nodeData[id].width = width;
                    this.$nodeData[id].height = height;
                }
            }
            if (this.$editable) {
                this.$nodeData[id].alt = true;
            }
            //重画转换线
            this.resetLines(id, this.$nodeData[id]);
        }
        else if (type === "line") {//如果是线
            this.$lineData[id].setInfo = setInfo;
            if (!this.$lineData[id]) return;
            if (this.$lineData[id].name === name) return;
            if (typeof this.onItemRename === 'function' && this.onItemRename(id, name, "node") === false) return;
            oldName = this.$lineData[id].name;
            this.$lineData[id].name = name;
            if (GooFlow.prototype.useSVG !== "") {
                this.$lineDom[id].childNodes[2].textContent = name;
            }
            else {
                this.$lineDom[id].childNodes[1].innerHTML = name;
                var n = this.$lineDom[id].getAttribute("fromTo").split(",");
                var x;
                if (this.$lineData[id].type !== "lr") {
                    x = (n[2] - n[0]) / 2;
                }
                else {
                    var Min = n[2] > n[0] ? n[0] : n[2];
                    if (Min > this.$lineData[id].M) Min = this.$lineData[id].M;
                    x = this.$lineData[id].M - Min;
                }
                if (x < 0) x = x * -1;
                this.$lineDom[id].childNodes[1].style.left = x - this.$lineDom[id].childNodes[1].offsetWidth / 2 + 4 + "px";
            }
            if (this.$editable) {
                this.$lineData[id].alt = true;
            }
        }
        else if (type === "area") {//如果是分组区域
            if (!this.$areaData[id]) return;
            if (this.$areaData[id].name === name) return;
            if (typeof this.onItemRename === 'function' && this.onItemRename(id, name, "node") === false) return;
            oldName = this.$areaData[id].name;
            this.$areaData[id].name = name;
            this.$areaDom[id].children("label").text(name);
            if (this.$editable) {
                this.$areaData[id].alt = true;
            }
        }
        if (this.$undoStack) {
            var paras = [id, oldName, type];
            this.pushOper("setName", paras);
        }
    },
    //设置结点的尺寸,仅支持非开始/结束结点
    resizeNode: function (id, width, height) {
        if (!this.$nodeData[id]) return;
        if (typeof this.onItemResize === 'function' && this.onItemResize(id, "node", width, height) === false) return;
        if (this.$nodeData[id].type === "start" || this.$nodeData[id].type === "end") return;
        if (this.$undoStack) {
            var paras = [id, this.$nodeData[id].width, this.$nodeData[id].height];
            this.pushOper("resizeNode", paras);
        }

        this.$nodeDom[id].children("table").css({ width: (width - 2) * this.$scale + "px", height: (height - 2) * this.$scale + "px" });
        //确保因内部文字太多而撑大时，宽高尺寸仍然是精确的
        width = this.$nodeDom[id].outerWidth();
        height = this.$nodeDom[id].outerHeight();
        this.$nodeDom[id].children("table").css({ width: width - 2 + "px", height: height - 2 + "px" });
        //确保因内部文字太多而撑大时，宽高尺寸仍然是精确的 END
        this.$nodeData[id].width = width;
        this.$nodeData[id].height = height;
        if (this.$editable) {
            this.$nodeData[id].alt = true;
        }
        //重画转换线
        this.resetLines(id, this.$nodeData[id]);
        this._node2Area(id);
    },
    //删除结点
    delNode: function (id, trigger) {
        if (!this.$nodeData[id]) return;
        if (false !== trigger && typeof this.onItemDel === 'function' && this.onItemDel(id, "node") === false) return;
        //先删除可能的连线
        for (var k in this.$lineData) {
            if (this.$lineData[k].from === id || this.$lineData[k].to === id) {
                //this.$draw.removeChild(this.$lineDom[k]);
                //delete this.$lineData[k];
                //delete this.$lineDom[k];
                this.delLine(k, false);
            }
        }
        //再删除结点本身
        if (this.$undoStack) {
            var paras = [id, this.$nodeData[id]];
            this.pushOper("addNode", paras);
        }
        delete this.$nodeData[id];
        this.$nodeDom[id].remove();
        delete this.$nodeDom[id];
        --this.$nodeCount;
        if (this.$focus === id) this.$focus = "";

        if (this.$editable) {
            //在回退新增操作时,如果节点ID以this.$id+"_node_"开头,则表示为本次编辑时新加入的节点,这些节点的删除不用加入到$deletedItem中
            //if(id.indexOf(this.$id+"_node_")<0)
            this.$deletedItem[id] = "node";
        }
    },
    //设置流程图的名称
    setTitle: function (text) {
        this.$title = text;
        if (this.$head) this.$head.children("label").attr("title", text).text(text);
    },
    //仅供内部使用：计算流程图的实际宽高（单位像素）
    _suitSize: function () {
        var maxW = 0, maxH = 0;
        for (var k1 in this.$nodeData) {
            var node = this.$nodeData[k1];
            if (maxW < node.width + node.left) {
                maxW = node.width + node.left;
            }
            if (maxH < node.height + node.top) {
                maxH = node.height + node.top;
            }
        }
        for (var k2 in this.$areaData) {
            var area = this.$areaData[k2];
            if (maxW < area.width + area.left) {
                maxW = area.width + area.left;
            }
            if (maxH < area.height + area.top) {
                maxH = area.height + area.top;
            }
        }
        for (var k3 in this.$lineData) {
            var line = this.$lineData[k3];
            if (line.M && line.type === "lt" && maxW < line.M) {
                maxW = M + 4;
            }
            if (line.M && line.type === "tb" && maxH < line.M) {
                maxH = M + 4;
            }
        }
        return { width: maxW, height: maxH }

    },
    //载入一组数据
    loadData: function (data) {
        this.clearData();  //载入之前先清空数据 yubaolee
        var t = this.$editable;
        this.$editable = false;
        if (data.title) this.setTitle(data.title);
        if (data.initNum) this.$max = data.initNum;

        if (data != "") {
            var length, k;
            for (k = 0, length = data.nodes.length; k < length; k++) {
                this.addNode(data.nodes[k].id, data.nodes[k]);
            }
            for (k = 0, length = data.lines.length; k < length; k++) {
                this.addLine(data.lines[k].id, data.lines[k]);
            }
            for (k = 0, length = data.areas.length; k < length; k++) {
                this.addArea(data.areas[k].id, data.areas[k]);
            }
        }


        this.$editable = t;
        this.$deletedItem = {};
        //自行重构工作区，使之大小自适应
        var width = this.$workArea.width();
        var height = this.$workArea.height();
        var max = this._suitSize();
        while (max.width > width) {
            width += this.$workExtendStep;
        }
        while (max.height > height) {
            height += this.$workExtendStep;
        }
        this.$workArea.css({ height: height + "px", width: width + "px" });
        if (GooFlow.prototype.useSVG === "") {
            this.$draw.coordsize = width + "," + height;
        }
        this.$draw.style.width = width + "px";
        this.$draw.style.height = height + "px";
        if (this.$group != null) {
            this.$group.css({ height: height + "px", width: width + "px" });
        }
    },
    //用AJAX方式，远程读取一组数据
    //参数para为JSON结构，与JQUERY中$.ajax()方法的传参一样
    loadDataAjax: function (para) {
        var This = this;
        $.ajax({
            type: para.type,
            url: para.url,
            dataType: "json",
            data: para.data,
            success: function (msg) {
                if (para['dataFilter']) para['dataFilter'](msg, "json");
                This.loadData(msg);
                if (para.success) para.success(msg);
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {
                if (para.error) para.error(textStatus, errorThrown);
            }
        })
    },
    //把画好的整个流程图导出到一个变量中(其实也可以直接访问GooFlow对象的$nodeData,$lineData,$areaData这三个JSON属性)
    exportData: function () {
        var ret = {};
        ret.title = this.$title;
        ret.nodes = [];
        ret.lines = [];
        ret.areas = [];
        ret.initNum = this.$max;
        for (var k1 in this.$nodeData) {
            if (!this.$nodeData[k1].marked) {
                delete this.$nodeData[k1]["marked"];
            }
            ret.nodes.push(JSON.parse(JSON.stringify(this.$nodeData[k1])));
            //ret.nodes[k1]=JSON.parse(JSON.stringify(this.$nodeData[k1]));
        }
        for (var k2 in this.$lineData) {
            if (!this.$lineData[k2].marked) {
                delete this.$lineData[k2]["marked"];
            }
            ret.lines.push(JSON.parse(JSON.stringify(this.$lineData[k2])));
            // ret.lines[k2]=JSON.parse(JSON.stringify(this.$lineData[k2]));
        }
        for (var k3 in this.$areaData) {
            if (!this.$areaData[k3].marked) {
                delete this.$areaData[k3]["marked"];
            }

            ret.areas.push(JSON.parse(JSON.stringify(this.$areaData[k3])));
            // ret.areas[k3]=JSON.parse(JSON.stringify(this.$areaData[k3]));
        }
        return ret;
    },
    //只把本次编辑流程图中作了变更(包括增删改)的元素导出到一个变量中,以方便用户每次编辑载入的流程图后只获取变更过的数据
    exportAlter: function () {
        var ret = { nodes: {}, lines: {}, areas: {} };
        for (var k1 in this.$nodeData) {
            if (this.$nodeData[k1].alt) {
                ret.nodes[k1] = this.$nodeData[k1];
            }
        }
        for (var k2 in this.$lineData) {
            if (this.$lineData[k2].alt) {
                ret.lines[k2] = this.$lineData[k2];
            }
        }
        for (var k3 in this.$areaData) {
            if (this.$areaData[k3].alt) {
                ret.areas[k3] = this.$areaData[k3];
            }
        }
        ret.deletedItem = this.$deletedItem;
        return ret;
    },
    //变更元素的ID,一般用于快速保存后,将后台返回新元素的ID更新到页面中;type为元素类型(节点,连线,区块)
    transNewId: function (oldId, newId, type) {
        var tmp;
        switch (type) {
            case "node":
                if (this.$nodeData[oldId]) {
                    tmp = this.$nodeData[oldId];
                    delete this.$nodeData[oldId];
                    this.$nodeData[newId] = tmp;
                    tmp = this.$nodeDom[oldId].attr("id", newId);
                    delete this.$nodeDom[oldId];
                    this.$nodeDom[newId] = tmp;
                }
                break;
            case "line":
                if (this.$lineData[oldId]) {
                    tmp = this.$lineData[oldId];
                    delete this.$lineData[oldId];
                    this.$lineData[newId] = tmp;
                    tmp = this.$lineDom[oldId].attr("id", newId);
                    delete this.$lineDom[oldId];
                    this.$lineDom[newId] = tmp;
                }
                break;
            case "area":
                if (this.$areaData[oldId]) {
                    tmp = this.$areaData[oldId];
                    delete this.$areaData[oldId];
                    this.$areaData[newId] = tmp;
                    tmp = this.$areaDom[oldId].attr("id", newId);
                    delete this.$areaDom[oldId];
                    this.$areaDom[newId] = tmp;
                }
                break;
        }
    },
    //清空工作区及已载入的数据
    clearData: function () {
        for (var k1 in this.$nodeData) {
            this.delNode(k1);
        }
        for (var k2 in this.$lineData) {
            this.delLine(k2);
        }
        for (var k3 in this.$areaData) {
            this.delArea(k3);
        }
        this.$deletedItem = {};
    },
    //销毁自己
    destrory: function () {
        this.$bgDiv.empty();
        this.$lineData = null;
        this.$nodeData = null;
        this.$lineDom = null;
        this.$nodeDom = null;
        this.$areaDom = null;
        this.$areaData = null;
        this.$nodeCount = 0;
        this.$areaCount = 0;
        this.$areaCount = 0;
        this.$deletedItem = {};
    },
    ///////////以下为有关画线的方法
    //绘制一条箭头线，并返回线的DOM
    drawLine: function (id, sp, ep, mark, dash, $scale) {
        var line, text;
        var x = (ep[0] + sp[0]) / 2, y = (ep[1] + sp[1]) / 2;
        if (GooFlow.prototype.useSVG !== "") {
            line = document.createElementNS("http://www.w3.org/2000/svg", "g");
            var hi = document.createElementNS("http://www.w3.org/2000/svg", "path");
            var path = document.createElementNS("http://www.w3.org/2000/svg", "path");

            if (id !== "") line.setAttribute("id", id);
            line.setAttribute("from", sp[0] + "," + sp[1]);
            line.setAttribute("to", ep[0] + "," + ep[1]);
            hi.setAttribute("visibility", "hidden");
            hi.setAttribute("stroke-width", '9');
            hi.setAttribute("fill", "none");
            hi.setAttribute("stroke", "white");
            hi.setAttribute("d", "M " + sp[0] + " " + sp[1] + " L " + ep[0] + " " + ep[1]);
            hi.setAttribute("pointer-events", "stroke");
            path.setAttribute("d", "M " + sp[0] + " " + sp[1] + " L " + ep[0] + " " + ep[1]);
            path.setAttribute("stroke-width", mark ? '2.4' : '1.4');
            path.setAttribute("stroke-linecap", "round");
            path.setAttribute("fill", "none");
            if (dash) path.setAttribute("style", "stroke-dasharray:6,5");
            if (mark) {
                path.setAttribute("stroke", GooFlow.color.mark);
                path.setAttribute("marker-end", "url(#arrow2)");
            }
            else {
                path.setAttribute("stroke", GooFlow.color.line);
                path.setAttribute("marker-end", "url(#arrow1)");
            }
            line.appendChild(hi);
            line.appendChild(path);
            line.style.cursor = "crosshair";
            if (id !== "" && id !== "GooFlow_tmp_line") {
                text = document.createElementNS("http://www.w3.org/2000/svg", "text");
                text.setAttribute("fill", GooFlow.color.lineFont);
                line.appendChild(text);

                text.setAttribute("text-anchor", "middle");
                text.setAttribute("x", x + '');
                text.setAttribute("y", y + '');
                text.style.cursor = "text";
                text.style.fontSize = 14 * $scale + "px";
                line.style.cursor = "pointer";
            }
        } else {
            line = document.createElement("v:polyline");
            if (id !== "") line.id = id;
            //line.style.position="absolute";
            line.points.value = sp[0] + "," + sp[1] + " " + ep[0] + "," + ep[1];
            line.setAttribute("fromTo", sp[0] + "," + sp[1] + "," + ep[0] + "," + ep[1]);
            line.strokeWeight = "1.2";
            line.stroke.EndArrow = "Block";
            line.style.cursor = "crosshair";
            if (id !== "" && id !== "GooFlow_tmp_line") {
                text = document.createElement("div");
                //text.innerHTML=id;
                line.appendChild(text);
                if (x < 0) x = x * -1;
                if (y < 0) y = y * -1;
                text.style.left = x + "px";
                text.style.top = y - 6 + "px";
                text.style.color = GooFlow.color.lineFont;
                text.style.fontSize = 14 * $scale + "px";
                line.style.cursor = "pointer";
            }
            if (dash) line.stroke.dashStyle = "Dash";
            if (mark) line.strokeColor = GooFlow.color.mark;
            else line.strokeColor = GooFlow.color.line;
            line.fillColor = GooFlow.color.line;
        }
        return line;
    },
    //画一条只有两个中点的折线
    drawPoly: function (id, sp, m1, m2, ep, mark, dash, $scale) {
        var poly, strPath, text;
        var x = (m2[0] + m1[0]) / 2, y = (m2[1] + m1[1]) / 2;
        if (GooFlow.prototype.useSVG !== "") {
            poly = document.createElementNS("http://www.w3.org/2000/svg", "g");
            var hi = document.createElementNS("http://www.w3.org/2000/svg", "path");
            var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
            if (id !== "") poly.setAttribute("id", id);
            poly.setAttribute("from", sp[0] + "," + sp[1]);
            poly.setAttribute("to", ep[0] + "," + ep[1]);
            hi.setAttribute("visibility", "hidden");
            hi.setAttribute("stroke-width", '9');
            hi.setAttribute("fill", "none");
            hi.setAttribute("stroke", "white");
            strPath = "M " + sp[0] + " " + sp[1];
            if (m1[0] !== sp[0] || m1[1] !== sp[1])
                strPath += " L " + m1[0] + " " + m1[1];
            if (m2[0] !== ep[0] || m2[1] !== ep[1])
                strPath += " L " + m2[0] + " " + m2[1];
            strPath += " L " + ep[0] + " " + ep[1];
            hi.setAttribute("d", strPath);
            hi.setAttribute("pointer-events", "stroke");
            path.setAttribute("d", strPath);
            path.setAttribute("stroke-width", mark ? '2.4' : '1.4');
            path.setAttribute("stroke-linecap", "round");
            path.setAttribute("fill", "none");
            if (dash) path.setAttribute("style", "stroke-dasharray:6,5");
            if (mark) {
                path.setAttribute("stroke", GooFlow.color.mark);
                path.setAttribute("marker-end", "url(#arrow2)");
            }
            else {
                path.setAttribute("stroke", GooFlow.color.line);
                path.setAttribute("marker-end", "url(#arrow1)");
            }
            poly.appendChild(hi);
            poly.appendChild(path);
            text = document.createElementNS("http://www.w3.org/2000/svg", "text");
            text.setAttribute("fill", GooFlow.color.lineFont);
            poly.appendChild(text);
            text.setAttribute("text-anchor", "middle");
            text.setAttribute("x", x + '');
            text.setAttribute("y", y + '');
            text.style.cursor = "text";
        }
        else {
            poly = document.createElement("v:Polyline");
            if (id !== "") poly.id = id;
            poly.filled = "false";
            strPath = sp[0] + "," + sp[1];
            if (m1[0] !== sp[0] || m1[1] !== sp[1])
                strPath += " " + m1[0] + "," + m1[1];
            if (m2[0] !== ep[0] || m2[1] !== ep[1])
                strPath += " " + m2[0] + "," + m2[1];
            strPath += " " + ep[0] + "," + ep[1];
            poly.points.value = strPath;
            poly.setAttribute("fromTo", sp[0] + "," + sp[1] + "," + ep[0] + "," + ep[1]);
            poly.strokeWeight = mark ? "2.4" : "1.2";
            poly.stroke.EndArrow = "Block";
            text = document.createElement("div");
            //text.innerHTML=id;
            poly.appendChild(text);
            if (x < 0) x = x * -1;
            if (y < 0) y = y * -1;
            text.style.left = x + "px";
            text.style.top = y - 4 + "px";
            text.style.color = GooFlow.color.lineFont;
            if (dash) poly.stroke.dashStyle = "Dash";
            if (mark) poly.strokeColor = GooFlow.color.mark;
            else poly.strokeColor = GooFlow.color.line;
        }
        poly.style.cursor = "pointer";
        text.style.fontSize = 14 * $scale + "px";
        return poly;
    },
    //原lineData已经设定好的情况下，只在绘图工作区画一条线的页面元素
    addLineDom: function (id, lineData) {
        var n1 = this.$nodeData[lineData.from], n2 = this.$nodeData[lineData.to];//获取开始/结束结点的数据
        if (!n1 || !n2) return;
        //开始计算线端点坐标
        var res;
        if (lineData.type && lineData.type !== "sl")
            res = calcPolyPoints(n1, n2, lineData.type, lineData.M, this.$scale);
        else
            res = calcStartEnd(n1, n2, this.$scale);
        if (!res) return;

        if (lineData.type === "sl")
            this.$lineDom[id] = GooFlow.prototype.drawLine(id, res.start, res.end, lineData.marked, lineData.dash, this.$scale);
        else
            this.$lineDom[id] = GooFlow.prototype.drawPoly(id, res.start, res.m1, res.m2, res.end, lineData.marked, lineData.dash, this.$scale);
        this.$draw.appendChild(this.$lineDom[id]);
        if (GooFlow.prototype.useSVG === "") {
            this.$lineDom[id].childNodes[1].innerHTML = lineData.name;
            if (lineData.type !== "sl") {
                var Min = (res.start[0] > res.end[0] ? res.end[0] : res.start[0]);
                if (Min > res.m2[0]) Min = res.m2[0];
                if (Min > res.m1[0]) Min = res.m1[0];
                this.$lineDom[id].childNodes[1].style.left = (res.m2[0] + res.m1[0]) / 2 - Min - this.$lineDom[id].childNodes[1].offsetWidth / 2 + 4;
                Min = (res.start[1] > res.end[1] ? res.end[1] : res.start[1]);
                if (Min > res.m2[1]) Min = res.m2[1];
                if (Min > res.m1[1]) Min = res.m1[1];
                this.$lineDom[id].childNodes[1].style.top = (res.m2[1] + res.m1[1]) / 2 - Min - this.$lineDom[id].childNodes[1].offsetHeight / 2;
            } else
                this.$lineDom[id].childNodes[1].style.left =
                    ((res.end[0] - res.start[0]) * (res.end[0] > res.start[0] ? 1 : -1) - this.$lineDom[id].childNodes[1].offsetWidth) / 2 + 4;
        }
        else {
            this.$lineDom[id].childNodes[2].textContent = lineData.name;
        }
    },
    //增加一条线
    addLine: function (id, json) {
        if (json.id == undefined) {
            $.extend(json, { id: id });
        }
        if (typeof this.onItemAdd === 'function' && this.onItemAdd(id, "line", json) === false) return;
        if (this.$undoStack && this.$editable) {
            this.pushOper("delLine", [id]);
        }
        if (json.from === json.to) return;
        var n1 = this.$nodeData[json.from], n2 = this.$nodeData[json.to];//获取开始/结束结点的数据
        if (!n1 || !n2) return;
        //避免两个节点间不能有一条以上同向接连线
        for (var k in this.$lineData) {
            if ((json.from === this.$lineData[k].from && json.to === this.$lineData[k].to && json.dash === this.$lineData[k].dash))
                return;
        }
        //设置$lineData[id]
        this.$lineData[id] = {};
        if (json.type) {
            this.$lineData[id].type = json.type;
            this.$lineData[id].M = json.M;
        }
        else this.$lineData[id].type = "sl";//默认为直线
        this.$lineData[id].from = json.from;
        this.$lineData[id].to = json.to;
        this.$lineData[id].id = json.id;  //赋值ID
        this.$lineData[id].setInfo = json.setInfo;
        this.$lineData[id].name = json.name;
        if (json.marked) this.$lineData[id].marked = json.marked;
        else this.$lineData[id].marked = false;
        if (json.dash) this.$lineData[id].dash = json.dash;
        else this.$lineData[id].dash = false;
        //设置$lineData[id]完毕

        this.addLineDom(id, this.$lineData[id]);

        ++this.$lineCount;
        if (this.$editable) {
            this.$lineData[id].alt = true;
            if (this.$deletedItem[id]) delete this.$deletedItem[id];//在回退删除操作时,去掉该元素的删除记录
        }
    },
    //重构所有连向某个结点的线的显示，传参结构为$nodeData数组的一个单元结构
    resetLines: function (id, node) {
        for (var i in this.$lineData) {
            var other = null;//获取结束/开始结点的数据
            var res;
            if (this.$lineData[i].from === id) {//找结束点
                other = this.$nodeData[this.$lineData[i].to] || null;
                if (other == null) continue;
                if (this.$lineData[i].type === "sl")
                    res = calcStartEnd(node, other, this.$scale);
                else
                    res = calcPolyPoints(node, other, this.$lineData[i].type, this.$lineData[i].M, this.$scale);
                if (!res) break;
            }
            else if (this.$lineData[i].to === id) {//找开始点
                other = this.$nodeData[this.$lineData[i].from] || null;
                if (other == null) continue;
                if (this.$lineData[i].type === "sl")
                    res = calcStartEnd(other, node, this.$scale);
                else
                    res = calcPolyPoints(other, node, this.$lineData[i].type, this.$lineData[i].M, this.$scale);
                if (!res) break;
            }
            if (other == null) continue;
            this.$draw.removeChild(this.$lineDom[i]);
            if (this.$lineData[i].type === "sl") {
                this.$lineDom[i] = GooFlow.prototype.drawLine(i, res.start, res.end, this.$lineData[i].marked, this.$lineData[i].dash, this.$scale);
            }
            else {
                this.$lineDom[i] = GooFlow.prototype.drawPoly(i, res.start, res.m1, res.m2, res.end, this.$lineData[i].marked, this.$lineData[i].dash, this.$scale);
            }
            this.$draw.appendChild(this.$lineDom[i]);
            if (GooFlow.prototype.useSVG === "") {
                this.$lineDom[i].childNodes[1].innerHTML = this.$lineData[i].name;
                if (this.$lineData[i].type !== "sl") {
                    var Min = (res.start[0] > res.end[0] ? res.end[0] : res.start[0]);
                    if (Min > res.m2[0]) Min = res.m2[0];
                    if (Min > res.m1[0]) Min = res.m1[0];
                    this.$lineDom[i].childNodes[1].style.left = (res.m2[0] + res.m1[0]) / 2 - Min - this.$lineDom[i].childNodes[1].offsetWidth / 2 + 4;
                    Min = (res.start[1] > res.end[1] ? res.end[1] : res.start[1]);
                    if (Min > res.m2[1]) Min = res.m2[1];
                    if (Min > res.m1[1]) Min = res.m1[1];
                    this.$lineDom[i].childNodes[1].style.top = (res.m2[1] + res.m1[1]) / 2 - Min - this.$lineDom[i].childNodes[1].offsetHeight / 2 - 4;
                } else
                    this.$lineDom[i].childNodes[1].style.left =
                        ((res.end[0] - res.start[0]) * (res.end[0] > res.start[0] ? 1 : -1) - this.$lineDom[i].childNodes[1].offsetWidth) / 2 + 4;
            }
            else this.$lineDom[i].childNodes[2].textContent = this.$lineData[i].name;
        }
    },
    //重新设置连线的样式 newType= "sl":直线, "lr":中段可左右移动型折线, "tb":中段可上下移动型折线
    setLineType: function (id, newType, M) {
        if (!newType || newType == null || newType === "" || newType === this.$lineData[id].type) return false;
        if (typeof this.onLineSetType === 'function' && this.onLineSetType(id, newType) === false) return;
        if (this.$undoStack) {
            var paras = [id, this.$lineData[id].type, this.$lineData[id].M];
            this.pushOper("setLineType", paras);
        }
        var from = this.$lineData[id].from;
        var to = this.$lineData[id].to;
        this.$lineData[id].type = newType;
        var res;
        //如果是变成折线
        if (newType !== "sl") {
            //res=calcPolyPoints(this.$nodeData[from],this.$nodeData[to],this.$lineData[id].type,this.$lineData[id].M, this.$scale);
            if (M) {
                this.setLineM(id, M, true);
            } else {
                this.setLineM(id, getMValue(this.$nodeData[from], this.$nodeData[to], newType), true);
            }
        }
        //如果是变回直线
        else {
            delete this.$lineData[id].M;
            this.$lineMove.hide().removeData("type").removeData("tid");
            res = calcStartEnd(this.$nodeData[from], this.$nodeData[to], this.$scale);
            if (!res) return;
            this.$draw.removeChild(this.$lineDom[id]);
            this.$lineDom[id] = GooFlow.prototype.drawLine(id, res.start, res.end, this.$lineData[id].marked, this.$lineData[id].dash, this.$scale);
            this.$draw.appendChild(this.$lineDom[id]);
            if (GooFlow.prototype.useSVG === "") {
                this.$lineDom[id].childNodes[1].innerHTML = this.$lineData[id].name;
                this.$lineDom[id].childNodes[1].style.left =
                    ((res.end[0] - res.start[0]) * (res.end[0] > res.start[0] ? 1 : -1) - this.$lineDom[id].childNodes[1].offsetWidth) / 2 + 4;
            }
            else
                this.$lineDom[id].childNodes[2].textContent = this.$lineData[id].name;
        }
        if (this.$focus === id) {
            this.focusItem(id);
        }
        if (this.$editable) {
            this.$lineData[id].alt = true;
        }
    },
    //设置折线中段的X坐标值（可左右移动时）或Y坐标值（可上下移动时）
    setLineM: function (id, M, noStack) {
        if (!this.$lineData[id] || M < 0 || !this.$lineData[id].type || this.$lineData[id].type === "sl") return false;
        if (typeof this.onLineMove === 'function' && this.onLineMove(id, M) === false) return false;
        if (this.$undoStack && !noStack) {
            var paras = [id, this.$lineData[id].M];
            this.pushOper("setLineM", paras);
        }
        var from = this.$lineData[id].from;
        var to = this.$lineData[id].to;
        this.$lineData[id].M = M;
        var ps = calcPolyPoints(this.$nodeData[from], this.$nodeData[to], this.$lineData[id].type, this.$lineData[id].M, this.$scale);
        this.$draw.removeChild(this.$lineDom[id]);
        this.$lineDom[id] = GooFlow.prototype.drawPoly(id, ps.start, ps.m1, ps.m2, ps.end, this.$lineData[id].marked, this.$lineData[id].dash, this.$scale);
        this.$draw.appendChild(this.$lineDom[id]);
        if (GooFlow.prototype.useSVG === "") {
            this.$lineDom[id].childNodes[1].innerHTML = this.$lineData[id].name;
            var Min = (ps.start[0] > ps.end[0] ? ps.end[0] : ps.start[0]);
            if (Min > ps.m2[0]) Min = ps.m2[0];
            if (Min > ps.m1[0]) Min = ps.m1[0];
            this.$lineDom[id].childNodes[1].style.left = (ps.m2[0] + ps.m1[0]) / 2 - Min - this.$lineDom[id].childNodes[1].offsetWidth / 2 + 4;
            Min = (ps.start[1] > ps.end[1] ? ps.end[1] : ps.start[1]);
            if (Min > ps.m2[1]) Min = ps.m2[1];
            if (Min > ps.m1[1]) Min = ps.m1[1];
            this.$lineDom[id].childNodes[1].style.top = (ps.m2[1] + ps.m1[1]) / 2 - Min - this.$lineDom[id].childNodes[1].offsetHeight / 2 - 4;
        }
        else this.$lineDom[id].childNodes[2].textContent = this.$lineData[id].name;
        if (this.$editable) {
            this.$lineData[id].alt = true;
        }
    },
    //删除转换线
    delLine: function (id, trigger) {
        if (!this.$lineData[id]) return;
        if (false !== trigger && typeof this.onItemDel === 'function' && this.onItemDel(id, "node") === false) return;
        if (this.$undoStack) {
            var paras = [id, this.$lineData[id]];
            this.pushOper("addLine", paras);
        }
        this.$draw.removeChild(this.$lineDom[id]);
        delete this.$lineData[id];
        delete this.$lineDom[id];
        if (this.$focus === id) this.$focus = "";
        --this.$lineCount;
        if (this.$editable) {
            //在回退新增操作时,如果节点ID以this.$id+"_line_"开头,则表示为本次编辑时新加入的节点,这些节点的删除不用加入到$deletedItem中
            // if(id.indexOf(this.$id+"_line_")<0)
            this.$deletedItem[id] = "line";
            this.$mpFrom.hide().removeData("p");
            this.$mpTo.hide().removeData("p");
        }
        if (this.$lineOper) {
            this.$lineOper.hide().removeData("tid");
        }
    },
    //变更连线两个端点所连的结点
    //参数：要变更端点的连线ID，新的开始结点ID、新的结束结点ID；如果开始/结束结点ID是传入null或者""，则表示原端点不变
    moveLinePoints: function (lineId, newStart, newEnd, noStack) {
        if (newStart === newEnd) return;
        if (!lineId || !this.$lineData[lineId]) return;
        if (newStart == null || newStart === "")
            newStart = this.$lineData[lineId].from;
        if (newEnd == null || newEnd === "")
            newEnd = this.$lineData[lineId].to;

        //避免两个节点间不能有一条以上同向接连线
        for (var k in this.$lineData) {
            if ((newStart === this.$lineData[k].from && newEnd === this.$lineData[k].to))
                return;
        }
        if (typeof this.onLinePointMove === 'function' && this.onLinePointMove(lineId, newStart, newEnd) === false) return;
        if (this.$undoStack && !noStack) {
            var paras = [lineId, this.$lineData[lineId].from, this.$lineData[lineId].to];
            this.pushOper("moveLinePoints", paras);
        }
        if (newStart != null && newStart !== "") {
            this.$lineData[lineId].from = newStart;
        }
        if (newEnd != null && newEnd !== "") {
            this.$lineData[lineId].to = newEnd;
        }
        //重建转换线
        this.$draw.removeChild(this.$lineDom[lineId]);
        this.addLineDom(lineId, this.$lineData[lineId]);
        if (this.$editable) {
            this.$lineData[lineId].alt = true;
        }
    },

    //用颜色标注/取消标注一个结点或转换线，常用于显示重点或流程的进度。
    //这是一个在编辑模式中无用,但是在纯浏览模式中非常有用的方法，实际运用中可用于跟踪流程的进度。
    markItem: function (id, type, mark) {
        if (type === "node") {
            if (!this.$nodeData[id]) return;
            if (typeof this.onItemMark === 'function' && this.onItemMark(id, "node", mark) === false) return;
            this.$nodeData[id].marked = mark || false;
            if (mark) {
                this.$nodeDom[id].addClass("item_mark").css("border-color", GooFlow.color.mark);
            }
            else {
                this.$nodeDom[id].removeClass("item_mark");
                if (id !== this.$focus) this.$nodeDom[id].css("border-color", "transparent");
            }

        } else if (type === "line") {
            if (!this.$lineData[id]) return;
            if (this.onItemMark != null && !this.onItemMark(id, "line", mark)) return;
            this.$lineData[id].marked = mark || false;
            if (GooFlow.prototype.useSVG !== "") {
                if (mark) {
                    this.$lineDom[id].childNodes[1].setAttribute("stroke", GooFlow.color.mark);
                    this.$lineDom[id].childNodes[1].setAttribute("marker-end", "url(#arrow2)");
                    this.$lineDom[id].childNodes[1].setAttribute("stroke-width", 2.4);
                } else {
                    this.$lineDom[id].childNodes[1].setAttribute("stroke", GooFlow.color.line);
                    this.$lineDom[id].childNodes[1].setAttribute("marker-end", "url(#arrow1)");
                    this.$lineDom[id].childNodes[1].setAttribute("stroke-width", 1.4);
                }
            } else {
                if (mark) {
                    this.$lineDom[id].strokeColor = GooFlow.color.mark;
                    this.$lineDom[id].strokeWeight = "2.4";
                }
                else {
                    this.$lineDom[id].strokeColor = GooFlow.color.line;
                    this.$lineDom[id].strokeWeight = "1.2";
                }
            }
        }
        if (this.$undoStack) {
            var paras = [id, type, !mark];
            this.pushOper("markItem", paras);
        }
    },
    ////////////////////////以下为区域分组块操作
    //传入一个区域组(泳道)的ID，判断图中所有结点在此区域组(泳道)的范围内
    _areaFixNodes: function (areaId) {
        var area = this.$areaData[areaId];
        for (var key in this.$nodeData) {
            var node = this.$nodeData[key];
            if (node.left >= area.left && node.left < area.left + area.width &&
                node.top >= area.top && node.top < area.top + area.height
            ) {
                node.areaId = areaId;
            } else if (node.areaId && node.areaId === areaId) {
                this._node2Area(key);
            }
        }
    },
    moveArea: function (id, left, top) {
        if (!this.$areaData[id]) return;
        if (this.onItemMove != null && !this.onItemMove(id, "area", left, top)) return;
        if (this.$undoStack) {
            var paras = [id, this.$areaData[id].left, this.$areaData[id].top];
            this.pushOper("moveArea", paras);
        }
        if (left < 0) left = 0;
        if (top < 0) top = 0;
        $("#" + id).css({ left: left * this.$scale + "px", top: top * this.$scale + "px" });
        this.$areaData[id].left = left;
        this.$areaData[id].top = top;
        if (this.$editable) {
            this.$areaData[id].alt = true;
            this._areaFixNodes(id);
        }
    },
    //删除区域分组
    delArea: function (id, trigger) {
        if (!this.$areaData[id]) return;
        if (this.$undoStack) {
            var paras = [id, this.$areaData[id]];
            this.pushOper("addArea", paras);
        }
        if (false !== trigger && typeof this.onItemDel === 'function' && this.onItemDel(id, "node") === false) return;
        delete this.$areaData[id];
        this.$areaDom[id].remove();
        delete this.$areaDom[id];
        --this.$areaCount;
        if (this.$editable) {
            //在回退新增操作时,如果节点ID以this.$id+"_area_"开头,则表示为本次编辑时新加入的节点,这些节点的删除不用加入到$deletedItem中
            //if(id.indexOf(this.$id+"_area_")<0)
            for (var key in this.$nodeData) {
                var node = this.$nodeData[key];
                if (node.areaId === id) {
                    delete node.areaId
                }
            }
            this.$deletedItem[id] = "area";
        }
    },
    //设置区域分组的颜色
    setAreaColor: function (id, color) {
        if (!this.$areaData[id]) return;
        if (this.$undoStack) {
            var paras = [id, this.$areaData[id].color];
            this.pushOper("setAreaColor", paras);
        }
        if (color === "red" || color === "yellow" || color === "blue" || color === "green") {
            this.$areaDom[id].removeClass("area_" + this.$areaData[id].color).addClass("area_" + color);
            this.$areaData[id].color = color;
        }
        if (this.$editable) {
            this.$areaData[id].alt = true;
        }
    },
    //设置区域分块的尺寸
    resizeArea: function (id, width, height) {
        if (!this.$areaData[id]) return;
        if (typeof this.onItemResize === 'function' && this.onItemResize(id, "area", width, height) === false) return;
        if (this.$undoStack) {
            var paras = [id, this.$areaData[id].width, this.$areaData[id].height];
            this.pushOper("resizeArea", paras);
        }

        this.$areaDom[id].children(".bg").css({ width: width * this.$scale + "px", height: height * this.$scale + "px" });

        width = this.$areaDom[id].outerWidth();
        height = this.$areaDom[id].outerHeight();
        this.$areaDom[id].children("bg").css({ width: width + "px", height: height + "px" });

        this.$areaData[id].width = width;
        this.$areaData[id].height = height;
        if (this.$editable) {
            this.$areaData[id].alt = true;
            this._areaFixNodes(id);
        }
    },
    addArea: function (id, json) {
        if (typeof this.onItemAdd === 'function' && this.onItemAdd(id, "area", json) === false) return;
        if (this.$undoStack && this.$editable) {
            this.pushOper("delArea", [id]);
        }
        this.$areaDom[id] = $("<div id='" + id + "' class='GooFlow_area area_" + json.color
            + "' style='top:" + json.top * this.$scale + "px;left:" + json.left * this.$scale + "px'><div class='bg' style='width:" + (json.width * this.$scale) + "px;height:" + (json.height * this.$scale) + "px'></div>"
            + "<label>" + json.name + "</label><i></i><div><div class='rs_bottom'></div><div class='rs_right'></div><div class='rs_rb'></div><div class='rs_close'></div></div></div>");
        this.$areaData[id] = json;
        this.$group.append(this.$areaDom[id]);
        if (this.$nowType !== "group") this.$areaDom[id].children("div:eq(1)").css("display", "none");
        ++this.$areaCount;
        if (this.$editable) {
            this.$areaData[id].alt = true;
            this._areaFixNodes(id);
            if (this.$deletedItem[id]) delete this.$deletedItem[id];//在回退删除操作时,去掉该元素的删除记录
        }
    },
    //重构整个流程图设计器的宽高
    reinitSize: function (width, height) {
        var w = (width || this.$bgDiv.width());
        var h = (height || this.$bgDiv.height());
        this.$bgDiv.css({ height: h + "px", width: w + "px" });
        var headHeight = 0, hack = 8;
        if (this.$head != null) {
            headHeight = 26;
            hack = 5;
        }
        if (this.$tool != null) {
            this.$tool.css({ height: h - headHeight - hack + "px" });
            w -= 31;
        }
        w -= 9;
        h = h - headHeight - (this.$head != null ? 5 : 8);
        //this.$workArea.parent().css({height:h+"px",width:w+"px"});

        if (this.$workArea.width() > w) {
            w = this.$workArea.width();
        }
        if (this.$workArea.height() > h) {
            h = this.$workArea.height();
        }

        this.$workArea.css({ height: h + "px", width: w + "px" });
        if (GooFlow.prototype.useSVG === "") {
            this.$draw.coordsize = w + "," + h;
        }
        this.$draw.style.width = w + "px";
        this.$draw.style.height = h + "px";
        if (this.$group != null) {
            this.$group.css({ height: h + "px", width: w + "px" });
        }
    },
    //重设整个工作区内容的显示缩放比例，从0.5至4倍
    resetScale: function (scale) {
        if (!scale) scale = 1.0;
        else if (scale < 0.5) scale = 0.5;
        else if (scale > 4) scale = 4;
        //以上是固定死取值范围：不让用户缩放过大或过小，已免无意中影响的显示效果
        if (this.$scale === scale) return;
        var oldS = this.$scale;
        this.$scale = scale;
        var factor = oldS / scale;//因数（旧缩放比例除以新缩放比例）,元素的现有值除以该因子，就能得到新的缩放后的值
        var W = 0, H = 0, P = {};//宽、高、左及上的临时变量
        //开始正式的缩放（节点、连线、泳道块有宽高和定位，其它编辑工具元素则只有定位）（全部以左上角为原点）
        this.blurItem();
        //先缩放工作区
        W = this.$workArea.width() / factor;
        H = this.$workArea.height() / factor;
        this.$workArea.css({ "height": H + "px", "width": W + "px" });
        if (GooFlow.prototype.useSVG !== "") {

        } else {
            this.$draw.coordsize = W + "," + H;
        }
        this.$draw.style.width = W + "px";
        this.$draw.style.height = H + "px";
        if (this.$group != null) {
            this.$group.css({ height: H + "px", width: W + "px" });
        }
        //缩放节点
        var isWebkit = navigator.userAgent.toLowerCase().indexOf('webkit') > -1;
        this.$workArea.children(".GooFlow_item").each(function () {
            var This = $(this);
            P = This.position();
            This.css({ "left": P.left / factor + "px", "top": P.top / factor + "px" });
            This = This.children("table");
            W = This.outerWidth() / factor;
            H = This.outerHeight() / factor;
            This.css({ "width": W + "px", "height": H + "px" });
            var tmp = 18 * scale;
            This.find("td[class='ico']").css({ width: tmp + "px" });
            var newSize = {};
            if (tmp < 12 && isWebkit) {
                newSize["width"] = "18px"; newSize["height"] = "18px";
                newSize["font-size"] = "18px";
                newSize["transform"] = "scale(" + (tmp / 18) + ")";
                newSize["margin"] = -((18 - tmp) / 2) + "px";
            } else {
                newSize["width"] = tmp + "px"; newSize["height"] = tmp + "px";
                newSize["font-size"] = tmp + "px";
                newSize["transform"] = "none";
                newSize["margin"] = "0px auto";
            }
            This.find("td[class='ico']").children("i").css(newSize);

            tmp = 14 * scale;
            if (This.parent().find(".span").length === 1) {
                This.parent().css("border-radius", W / 2 + "px");
                This = This.parent().find(".span");
                This.css({ "font-size": tmp + "px" });
            } else {
                This = This.find("td:eq(1) div");
                newSize = {};
                if (tmp < 12 && isWebkit) {
                    newSize["font-size"] = "14px";
                    newSize["transform"] = "scale(" + (tmp / 14) + ")";
                    var mW = (W / scale - 18 - (W - 18 * scale)) / 2;
                    var mH = (H / scale - H) / 2;
                    newSize["margin"] = -mH + "px " + (-mW) + "px";
                } else {
                    newSize["transform"] = "none";
                    newSize["font-size"] = tmp + "px";
                    newSize["margin"] = "0px";
                }
                This.css(newSize);
            }
        });
        //缩放区域图
        var ifs = 16 * scale + 2;
        this.$group.children(".GooFlow_area").each(function () {
            var This = $(this);
            P = This.position();
            This.css({ "left": P.left / factor + "px", "top": P.top / factor + "px" });
            This = This.children("div:eq(0)");
            W = This.outerWidth() / factor;
            H = This.outerHeight() / factor;
            This.css({ "width": W + "px", "height": H + "px" });
            This.next("label").css({
                "font-size": 14 * scale + "px",
                "left": ifs + 3 + "px"
            }).next("i").css({
                "font-size": ifs - 2 + "px",
                width: ifs + "px",
                height: ifs + "px",
                "line-height": ifs + "px"
            });
        });
        //缩放连线
        for (var id in this.$lineDom) {
            this.$draw.removeChild(this.$lineDom[id]);
            delete this.$lineDom[id];
        }
        for (var key in this.$lineData) {
            this.addLineDom(key, this.$lineData[key]);
        }
    }
};
//默认的颜色样式
GooFlow.color = {
    //main:"#20A0FF",
    font: "#15428B",
    node: "#C0CCDA",
    line: "#1D8CE0",
    lineFont: "#777",
    mark: "#ff8800",
    mix: "#B6F700",
    mixFont: "#777"
};
//默认的文字说明注释内容
GooFlow.remarks = {
    headBtns: {},
    toolBtns: {},
    extendRight: undefined,
    extendBottom: undefined
};
//当不想使用jquery插件式初始化方法时，另一种通过直接调用GooFlow内部构造方法进行的初始化
GooFlow.init = function (selector, property) {
    return new GooFlow(selector, property);
};
//在初始化出一个对象前的公用方法：覆盖设定GooFlow默认的颜色定义
GooFlow.setColors = function (colors) {
    $.extend(GooFlow.color, colors);
};
//扩展GooFlow方法的扩展用接口，一般用在CMD,AMD
GooFlow.extend = function (json) {
    for (var funcName in json) {
        GooFlow.prototype[funcName] = json[funcName];
    }
};
//将此类的构造函数加入至JQUERY对象中
$.extend({
    createGooFlow: function (selector, property) {
        return new GooFlow(selector, property);
    }
});